Now that you have a thorough understand of how XML signatures work, let us look at one implementation of an API provided by Apache that allows a user to sign and verify digital signatures in a document. The source code is available at http://xml.apache.org/security/index.html. We will first sign a document named axisSignature.xml. Listing 15.2 demonstrates XML signatures for this document.
/* <http://www.apache.org/>. */ package org.apache.xml.security.samples; import java.io.*; import java.security.*; import java.security.cert.*; import java.util.*; import org.apache.xpath.XPathAPI; import org.w3c.dom.*; import org.apache.xml.security.algorithms.MessageDigestAlgorithm; import org.apache.xml.security.c14n.*; import org.apache.xml.security.exceptions.XMLSecurityException; import org.apache.xml.security.signature.*; import org.apache.xml.security.keys.*; import org.apache.xml.security.keys.content.*; import org.apache.xml.security.keys.content.x509.*; import org.apache.xml.security.keys.keyresolver.*; import org.apache.xml.security.keys.storage.*; import org.apache.xml.security.keys.storage.implementations.*; import org.apache.xml.security.utils.*; import org.apache.xml.security.transforms.*; import org.apache.xml.security.Init; import org.apache.xml.security.samples.utils.resolver.OfflineResolver; import org.apache.xml.serialize.*; /** * * @author $Author: geuerp $ */ public class AxisSigner { /** Field AXIS_SIGNATURE_FILENAME */ public static final String AXIS_SIGNATURE_FILENAME = "axisSignature.xml"; /** * Method main * * @param unused * @throws Exception */ public static void main(String unused[]) throws Exception { org.apache.xml.security.Init.init(); //J- String keystoreType = "JKS"; String keystoreFile = "data/org/apache/xml/security/samples/input/ keystore.jks"; String keystorePass = "xmlsecurity"; String privateKeyAlias = "test"; String privateKeyPass = "xmlsecurity"; String certificateAlias = "test"; File signatureFile = new File(AXIS_SIGNATURE_FILENAME); //J+ KeyStore ks = KeyStore.getInstance(keystoreType); FileInputStream fis = new FileInputStream(keystoreFile); ks.load(fis, keystorePass.toCharArray()); PrivateKey privateKey = (PrivateKey) ks.getKey(privateKeyAlias, privateKeyPass.toCharArray()); javax.xml.parsers.DocumentBuilderFactory dbf = javax.xml.parsers.DocumentBuilderFactory.newInstance(); dbf.setNamespaceAware(true); javax.xml.parsers.DocumentBuilder db = dbf.newDocumentBuilder(); org.w3c.dom.Document doc = db.newDocument(); /* * Start SOAP infrastructure code. This is to be made compatible with Axis. * */ String soapNS = "http://www.w3.org/2001/12/soap-envelope"; String env = "env"; String envPrefix = env + ":"; Element envelopeElement = doc.createElementNS(soapNS, envPrefix + "Envelope"); envelopeElement.setAttribute("xmlns:" + env, soapNS); doc.appendChild(envelopeElement); Element headerElem = doc.createElementNS(soapNS, envPrefix + "Header"); Element bodyElem = doc.createElementNS(soapNS, envPrefix + "Body"); envelopeElement.appendChild(doc.createTextNode("\n")); envelopeElement.appendChild(headerElem); envelopeElement.appendChild(doc.createTextNode("\n")); envelopeElement.appendChild(bodyElem); envelopeElement.appendChild(doc.createTextNode("\n")); bodyElem .appendChild(doc .createTextNode("This is signed together with its Body ancestor")); String SOAPSECNS = "http://schemas.xmlsoap.org/soap/security/2000-12"; String SOAPSECprefix = "SOAP-SEC"; bodyElem.setAttributeNS(SOAPSECNS, SOAPSECprefix + ":" + "id", "Body"); Element soapSignatureElem = doc.createElementNS(SOAPSECNS, SOAPSECprefix + ":" + "Signature"); envelopeElement.setAttribute("xmlns:" + SOAPSECprefix, SOAPSECNS); envelopeElement.setAttribute(env + ":" + "actor", "some-uri"); envelopeElement.setAttribute(env + ":" + "mustUnderstand", "1"); envelopeElement.appendChild(doc.createTextNode("\n")); headerElem.appendChild(soapSignatureElem); /* * * End SOAP infrastructure code. This is to be made compatible with Axis. */ String BaseURI = signatureFile.toURL().toString(); XMLSignature sig = new XMLSignature(doc, BaseURI, XMLSignature.ALGO_ID_SIGNATURE_DSA); soapSignatureElem.appendChild(sig.getElement()); { // sig.addDocument("#Body"); Transforms transforms = new Transforms(doc); transforms.addTransform(Transforms.TRANSFORM_ENVELOPED_SIGNATURE); sig.addDocument("", transforms); } { X509Certificate cert = (X509Certificate) ks.getCertificate(certificateAlias); sig.addKeyInfo(cert); sig.addKeyInfo(cert.getPublicKey()); sig.sign(privateKey); } FileOutputStream f = new FileOutputStream(signatureFile); XMLUtils.outputDOMc14nWithComments(doc, f); f.close(); System.out.println("Wrote signature to "+ BaseURI); for (int i = 0; i< sig.getSignedInfo().getSignedContentLength(); i++) { System.out.println("-- Signed Content follows--"); System.out .println(new String(sig.getSignedInfo().getSignedContentItem(i))); } } }
In the above example, we specify the keystore type as Java Keystore (JKS), with its location and password. Additionally, we specify the private-key password and alias used for the keystore. We then retrieve the private key from the keystore:
PrivateKey privateKey = (PrivateKey) ks.getKey(privateKeyAlias, privateKeyPass.toCharArray());
Once we have the private key, we create a new XML document:
javax.xml.parsers.DocumentBuilderFactory dbf = javax.xml.parsers.DocumentBuilderFactory.newInstance(); dbf.setNamespaceAware(true); javax.xml.parsers.DocumentBuilder db = dbf.newDocumentBuilder(); org.w3c.dom.Document doc = db.newDocument();
Because we are signing a SOAP message, we need additional elements in the SOAP header and body for our signature:
Element envelopeElement = doc.createElementNS(soapNS, envPrefix + "Envelope"); envelopeElement.setAttribute("xmlns:" + env, soapNS); doc.appendChild(envelopeElement); Element headerElem = doc.createElementNS(soapNS, envPrefix + "Header"); Element bodyElem = doc.createElementNS(soapNS, envPrefix + "Body"); envelopeElement.appendChild(doc.createTextNode("\n")); envelopeElement.appendChild(headerElem); envelopeElement.appendChild(doc.createTextNode("\n")); envelopeElement.appendChild(bodyElem); envelopeElement.appendChild(doc.createTextNode("\n")); bodyElem .appendChild(doc .createTextNode("This is signed together with its Body ancestor")); String SOAPSECNS = "http://schemas.xmlsoap.org/soap/security/2000-12"; String SOAPSECprefix = "SOAP-SEC"; bodyElem.setAttributeNS(SOAPSECNS, SOAPSECprefix + ":" + "id", "Body"); Element soapSignatureElem = doc.createElementNS(SOAPSECNS, SOAPSECprefix + ":" + "Signature"); envelopeElement.setAttribute("xmlns:" + SOAPSECprefix, SOAPSECNS); envelopeElement.setAttribute(env + ":" + "actor", "some-uri"); envelopeElement.setAttribute(env + ":" + "mustUnderstand", "1"); envelopeElement.appendChild(doc.createTextNode("\n")); headerElem.appendChild(soapSignatureElem);
We then create a new XML signature using DSA (two-way asymmetric) and SHA1 (one-way hash) as our algorithm:
String BaseURI = signatureFile.toURL().toString(); XMLSignature sig = new XMLSignature(doc, BaseURI, XMLSignature.ALGO_ID_SIGNATURE_DSA);
By incorporating a signature, we are changing the contents of the document, which will require performing transformations on it. The input to the first transform results in dereferencing the URI attribute of the reference element. The output from the last transform is the input for the DigestMethod algorithm:
Transforms transforms = new Transforms(doc); transforms.addTransform(Transforms.TRANSFORM_ENVELOPED_SIGNATURE); sig.addDocument("", transforms);
Next, we retrieve the X.509 certificate from our keystore and add the certificate's public key to the signature:
X509Certificate cert = (X509Certificate) ks.getCertificate(certificateAlias); sig.addKeyInfo(cert); sig.addKeyInfo(cert.getPublicKey());
Finally, we sign the document using the certificate's private key and write its output to the file:
sig.sign(privateKey); FileOutputStream f = new FileOutputStream(signatureFile); XMLUtils.outputDOMc14nWithComments(doc, f); f.close();
As you can see, signing an XML document is straightforward. In our example, the newly generated signature is stored separately from the original document in the local directory.
Now let us look at validating a digital signature using the Apache Axis verifier in Listing 15.3.
/* For more information on the Apache Software Foundation, please see * <http://www.apache.org/>. */ package org.apache.xml.security.samples; import java.io.*; import java.security.*; import java.security.cert.*; import java.util.*; import org.apache.xpath.CachedXPathAPI; import org.w3c.dom.*; import org.apache.xml.security.algorithms.MessageDigestAlgorithm; import org.apache.xml.security.c14n.*; import org.apache.xml.security.exceptions.XMLSecurityException; import org.apache.xml.security.signature.*; import org.apache.xml.security.keys.*; import org.apache.xml.security.keys.content.*; import org.apache.xml.security.keys.content.x509.*; import org.apache.xml.security.keys.keyresolver.*; import org.apache.xml.security.keys.storage.*; import org.apache.xml.security.keys.storage.implementations.*; import org.apache.xml.security.utils.*; import org.apache.xml.security.transforms.*; import org.apache.xml.security.Init; import org.apache.xml.security.samples.utils.resolver.OfflineResolver; import org.apache.xml.serialize.*; /** * * @author $Author: geuerp $ */ public class AxisVerifier { /** * Method main * * @param unused * @throws Exception */ public static void main(String unused[]) throws Exception { org.apache.xml.security.Init.init(); File signatureFile = new File(AxisSigner.AXIS_SIGNATURE_FILENAME); javax.xml.parsers.DocumentBuilderFactory dbf = javax.xml.parsers.DocumentBuilderFactory.newInstance(); dbf.setNamespaceAware(true); javax.xml.parsers.DocumentBuilder db = dbf.newDocumentBuilder(); org.w3c.dom.Document doc = db.parse(new FileInputStream(signatureFile)); String BaseURI = signatureFile.toURL().toString(); CachedXPathAPI xpathAPI = new CachedXPathAPI(); Element nsctx = doc.createElement("nsctx"); nsctx.setAttribute("xmlns:ds", Constants.SignatureSpecNS); Element signatureElem = (Element) xpathAPI.selectSingleNode(doc, "//ds:Signature", nsctx); XMLSignature sig = new XMLSignature(signatureElem, BaseURI); boolean verify = sig.checkSignatureValue(sig.getKeyInfo().getPublicKey()); System.out.println("The signature is" + (verify ? " " : " not ") + "valid"); for (int i = 0; i < sig.getSignedInfo().getSignedContentLength(); i++) { boolean thisOneWasSigned = sig.getSignedInfo().getVerificationResult(i); if (thisOneWasSigned) { System.out.println("-- Signed Content follows--"); System.out.println(new String(sig.getSignedInfo().getSignedContentItem(i))); } } System.out.println(""); System.out.println("Prior transforms"); System.out.println(new String(sig.getSignedInfo().getReferencedContentBeforeTransformsItem(0).getBytes())); } }
In the above example, the first several lines of code locate the signature within the XML document. Once the signature element is located, we will create an instance of the XMLSignature class and call its checkSignatureValue method:
XMLSignature sig = new XMLSignature(signatureElem, BaseURI); boolean verify = sig.checkSignatureValue(sig.getKeyInfo().getPublicKey());
The checkSignatureValue method takes as an argument an X.509 certificate, which was used to sign the document. Within this method, the public key that signed the document is retrieved and validates the signature in the element. If the signature cannot be validated using the retrieved public key, the document is assumed to be invalid. The document's signature is considered invalid if it was not signed by the corresponding private key and/or the contents have been tampered with.
Digital signatures provide strong signer authentication, which helps realize the goal of nonrepudiation when implementing a Web service architecture. When tied with other technologies, such as encryption, the notion of trust can be incorporated into a Web service.