Java-Based Implementation of Digital Signatures and X509 Certificates for Secure XML Document Processing

czetsuya
5 min readMay 3, 2023

Overview

XML Signature is a standard in the digital signature that allows the authentication and validation of data being sent as XML objects in web service communication. Java 6 introduces Java XML Digital Signature API, which allows the generation and validation of signatures in an XML message.

According to RFC 2828, a digital signature is a cryptographic value added to a data object to enable the recipient to verify its origin and integrity. The cryptographic digital signature API in JDK 6 is described in detail in a security lesson in the Java Tutorial.

An XML signature is a type of digital signature that has unique properties. It outlines a process and format for generating digital signatures in the XML format and offers various advanced features. For example, it allows for the signing of multiple pieces of data, either in binary or XML format, and it enables the use of various cryptographic signature algorithms.

An XML signature is capable of signing arbitrary data, whether it is in XML or binary format. Furthermore, it can sign only a portion or a subset of an XML document instead of the entire document. Uniform Resource Identifiers (URIs) identify the data to be signed. XML signatures can be categorized as one or more of three types based on their properties.

  1. A detached signature is a signature that covers data that exists outside of the Signature element. This data may be located in a different document or file or present within the same document as the signature, such as a related element.
  2. An enveloping signature is a signature that covers data that is contained within the Signature element itself. In other words, the data to be signed is enclosed within the signature element and the signature itself.
  3. An enveloped signature is a type of signature that covers data containing the Signature element. This means the signature is applied to the entire document or data, including the signature element.

Java API Architecture

The JSR 105 Java Community Process program established the Java XML Digital Signature API to encompass all necessary and suggested features of the W3C’s XML-Signature Syntax and Processing Recommendation. The API employs the Java Cryptography Service Provider Architecture, which enables the development of service provider implementations. Service providers create an XML mechanism that identifies the implementation’s XML-parsing mechanism. In Java SE 6’s implementation, Sun’s service provider supports the Document Object Model (DOM) mechanism. For additional details on service providers, refer to the XML Digital Signature API overview.

XML Signature

This article will utilize an example of an enveloped XML signature, which means that it is generated over the contents of an XML document, specifically a sample puchase-order.xml.

<?xml version="1.0"?>
<PurchaseOrder PurchaseOrderNumber="99503" OrderDate="1999-10-20">
<Address Type="Shipping">
<Name>Ellen Adams</Name>
<Street>123 Maple Street</Street>
<City>Mill Valley</City>
<State>CA</State>
<Zip>10999</Zip>
<Country>USA</Country>
</Address>
<Address Type="Billing">
<Name>Tai Yee</Name>
<Street>8 Oak Avenue</Street>
<City>Old Town</City>
<State>PA</State>
<Zip>95819</Zip>
<Country>USA</Country>
</Address>
<DeliveryNotes>Please leave packages in shed by driveway.</DeliveryNotes>
<Items>
<Item PartNumber="872-AA">
<ProductName>Lawnmower</ProductName>
<Quantity>1</Quantity>
<USPrice>148.95</USPrice>
<Comment>Confirm this is electric</Comment>
</Item>
<Item PartNumber="926-AA">
<ProductName>Baby Monitor</ProductName>
<Quantity>2</Quantity>
<USPrice>39.98</USPrice>
<ShipDate>1999-05-21</ShipDate>
</Item>
</Items>
</PurchaseOrder>

XML Example 1.

After applying the digital signature, our message should look like this.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<PurchaseOrder OrderDate="1999-10-20" PurchaseOrderNumber="99503">
<Address Type="Shipping">
<Name>Ellen Adams</Name>
<Street>123 Maple Street</Street>
<City>Mill Valley</City>
<State>CA</State>
<Zip>10999</Zip>
<Country>USA</Country>
</Address>
<Address Type="Billing">
<Name>Tai Yee</Name>
<Street>8 Oak Avenue</Street>
<City>Old Town</City>
<State>PA</State>
<Zip>95819</Zip>
<Country>USA</Country>
</Address>
<DeliveryNotes>Please leave packages in shed by driveway.</DeliveryNotes>
<Items>
<Item PartNumber="872-AA">
<ProductName>Lawnmower</ProductName>
<Quantity>1</Quantity>
<USPrice>148.95</USPrice>
<Comment>Confirm this is electric</Comment>
</Item>
<Item PartNumber="926-AA">
<ProductName>Baby Monitor</ProductName>
<Quantity>2</Quantity>
<USPrice>39.98</USPrice>
<ShipDate>1999-05-21</ShipDate>
</Item>
</Items>
<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/2001/04/xmldsig-more#rsa-sha256"/>
<Reference URI="">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<DigestValue>rKK35PJzAva2B92cG/cJp6J2BF5JUbKPS5ogYNJkgJo=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>dJ2yTNLTjE41e/q4GRAAHBjIJY+Ax3Qn00CyDRTtfdF1WzCUkxj9V7hpp/alwA3/NWeifHrJMr0F
ak8g41u90RcKV/9rX3lRxNyOKApAKpTw6CrAM6PTcHzHF/NWiL/At8v51AZYm55UgjjrB3xxHvUn
BidyBUtRuETIrikUD3hSMGD2u9prNhgAxWZ+QmRx2ga171/tnGo1B0JF+aBnup8kgq0I9Po3BSW3
a7gwAbVlV3R/pFIrht8wfOVjyzQgFnB+SCJ9UwdSwPJqfyrWxSGE5em3hjbX4VGTWeyhS56s0lX5
9vkCk6dw9cxIvyUsAVSTxYX5SRib2ekpIe7Oyg==
</SignatureValue>
<KeyInfo>
<X509Data>
<X509SubjectName>
1.2.840.113549.1.9.1=#161e6564776172642e6c6567617370695f65787440612d746f2d62652e636f6d,CN=localhost,OU=EETS,O=Atobe,ST=Lisboa,C=PT
</X509SubjectName>
<X509Certificate>MIIDiTCCAnECFAtZBLeU3vRAzm0tXHf7pdP07psNMA0GCSqGSIb3DQEBCwUAMIGAMQswCQYDVQQG
EwJQVDEPMA0GA1UECAwGTGlzYm9hMQ4wDAYDVQQKDAVBdG9iZTENMAsGA1UECwwERUVUUzESMBAG
A1UEAwwJbG9jYWxob3N0MS0wKwYJKoZIhvcNAQkBFh5lZHdhcmQubGVnYXNwaV9leHRAYS10by1i
ZS5jb20wHhcNMjMwMjIxMTAzNzM0WhcNMjQwMjIxMTAzNzM0WjCBgDELMAkGA1UEBhMCUFQxDzAN
BgNVBAgMBkxpc2JvYTEOMAwGA1UECgwFQXRvYmUxDTALBgNVBAsMBEVFVFMxEjAQBgNVBAMMCWxv
Y2FsaG9zdDEtMCsGCSqGSIb3DQEJARYeZWR3YXJkLmxlZ2FzcGlfZXh0QGEtdG8tYmUuY29tMIIB
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxpnILKjFkUsbaVhsI366Od6tfCFnZafsk0Vi
KW+k2z2bckig2+OhDr88KItst7LWORFRmo6JltHx1TfWtkYhboVEWOhw0/kVPCBqNdtJiiAyrj5l
pElgHW6Js8ECUgiiupoJeqwrDPCUbCF0MsOQX/swCbZMqQsAQrPRiNCiDviZ9V/aIlQEaDb7fDJT
wfM4pmPSPzMXPRghv58EKNiJQIpmQw9H0LYA5iu/kvV6nYC8ROF/4giTGhPr3qYMyR1WCa5EuFOO
NGUE8Hn8l43DMFa/hFxVECyYqw/wQQngvM0QDlli1K2FRyZYT7k/v+mqBiNYZC50hjkVtvO5Yd8m
sQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAnLELZRFMZ8FbLNuSWOqmrizOo7BkiT7gyNx1t6fMb
gadx8qnCpytbghr8O61QuzpYp+65hi9yDEDBfNDtvmT0olIhJK8tsVOu+857MEL0tNcNmavWI4qH
N0lJ5HZRjTm0VLz86UZdWFJfyk7H+sZGOLucDVc4+BzcpsCiW1Dyy+rdEeLNjwmsLvPP2ol16Mzc
Ij48aO2DLOut/+OaUl/4TNOrBKZq4Ve2F55RQUwvMDX0BLZlmDy3pI3XSho7KKDdNhl1/0AFJWVh
skZwR+fAK/RbU1fB9Obc9/IoSm6KANsJRCqFbfkS2hIhT3cvQs/cmR5ZRjgcFgl5J3sMCUFX
</X509Certificate>
</X509Data>
</KeyInfo>
</Signature>
</PurchaseOrder>

Here is the code that digitally signs the XML document.

package com.czetsuyatech.dsig.xml;

import com.czetsuyatech.dsig.SignXml;
import com.czetsuyatech.dsig.exceptions.SignatureNotFoundException;
import com.czetsuyatech.dsig.exceptions.XmlSigningException;
import jakarta.xml.bind.JAXBException;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableEntryException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.xml.crypto.MarshalException;
import javax.xml.crypto.dsig.CanonicalizationMethod;
import javax.xml.crypto.dsig.DigestMethod;
import javax.xml.crypto.dsig.Reference;
import javax.xml.crypto.dsig.SignatureMethod;
import javax.xml.crypto.dsig.SignedInfo;
import javax.xml.crypto.dsig.Transform;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureException;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMSignContext;
import javax.xml.crypto.dsig.dom.DOMValidateContext;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
import javax.xml.crypto.dsig.keyinfo.X509Data;
import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
import javax.xml.crypto.dsig.spec.TransformParameterSpec;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public class DigitalSigneeImpl implements XmlTransformer {

private final String keystoreFile;
private final String keystoreFormat;
private final String keystorePassword;
private final String privateKeyPassword;
private final String keyEntryAlias;
private final XMLSignatureFactory signatureFactory;

public DigitalSigneeImpl(String keystoreFile, String keystoreFormat, String keystorePassword,
String privateKeyPassword, String keyEntryAlias) {

this.keystoreFile = keystoreFile;
this.keystoreFormat = keystoreFormat;
this.keystorePassword = keystorePassword;
this.privateKeyPassword = privateKeyPassword;
this.keyEntryAlias = keyEntryAlias;

// Instantiate a DOM XMLSignatureFactory object to produce the enveloped signature
signatureFactory = XMLSignatureFactory.getInstance("DOM");
}

public Document sign(SignXml signXml) throws XmlSigningException {

// STEP 1

// Create a Reference to the document being signed by specifying an empty URI value to represent the entire
// document. Additionally, specify the SHA256 digest algorithm and use the ENVELOPED Transform
Reference ref;
try {
ref = signatureFactory.newReference("", signatureFactory.newDigestMethod(DigestMethod.SHA256, null),
Collections.singletonList(signatureFactory.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null)),
null, null);

// Create the SignedInfo
SignedInfo signedInfo =
signatureFactory.newSignedInfo(signatureFactory.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE,
(C14NMethodParameterSpec) null),
signatureFactory.newSignatureMethod(SignatureMethod.RSA_SHA256, null), Collections.singletonList(ref));

// STEP 2

// Load the KeyStore and retrieve the signing key and certificate
KeyStore ks = KeyStore.getInstance(keystoreFormat);

ks.load(getFileFromResourceAsStream(keystoreFile), keystorePassword.toCharArray());
KeyStore.PrivateKeyEntry keyEntry =
(KeyStore.PrivateKeyEntry) ks.getEntry(keyEntryAlias,
new KeyStore.PasswordProtection(privateKeyPassword.toCharArray()));
X509Certificate cert = (X509Certificate) keyEntry.getCertificate();

// Create the KeyInfo containing the X509Data
KeyInfoFactory kif = signatureFactory.getKeyInfoFactory();
List<Object> x509Content = new ArrayList<>();
x509Content.add(cert.getSubjectX500Principal().getName());
x509Content.add(cert);
X509Data xd = kif.newX509Data(x509Content);
KeyInfo ki = kif.newKeyInfo(Collections.singletonList(xd));

// STEP 3

// Create an instance of the document that will be signed
DocumentBuilderFactory dbf = getDocumentBuilderFactory();
dbf.setNamespaceAware(true);
//TO IMPL , temp change to be compilable
Document doc = dbf.newDocumentBuilder().parse(getInputStream(signXml));

// Initialize a DOMSignContext by providing the RSA PrivateKey and identifying the parent element of the resulting
// XMLSignature
DOMSignContext dsc = new DOMSignContext(keyEntry.getPrivateKey(), doc.getDocumentElement());

// Instantiate the XMLSignature object without signing it yet
XMLSignature signature = signatureFactory.newXMLSignature(signedInfo, ki);

// Marshal, generate, and sign the enveloped signature
signature.sign(dsc);

return doc;

} catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | ParserConfigurationException |
MarshalException | XMLSignatureException | UnrecoverableEntryException | KeyStoreException |
IOException | CertificateException | SAXException | JAXBException e) {
throw new XmlSigningException(e);
}
}

public boolean validate(Document doc) throws MarshalException, XMLSignatureException {

// Find Signature element
NodeList nl = doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
if (nl.getLength() == 0) {
throw new SignatureNotFoundException("Signature element not found");
}

// Create a DOMValidateContext and specify a KeySelector and document context
DOMValidateContext valContext = new DOMValidateContext(new X509KeySelector(), nl.item(0));
valContext.setProperty("org.jcp.xml.dsig.secureValidation", Boolean.TRUE);

// Unmarshal the signature
XMLSignature signature = signatureFactory.unmarshalXMLSignature(valContext);

// Validate the signature
boolean result = signature.validate(valContext);

if (!result) {
System.out.println("Signature failed core validation");
boolean sv = signature.getSignatureValue().validate(valContext);
System.out.println("signature validation status: {}" + sv);
if (!sv) {
// Check the validation status of each Reference.
for (Reference reference : signature.getSignedInfo().getReferences()) {
boolean refValid = reference.validate(valContext);
System.out.println("ref[{}] validity status: " + refValid);
}
} else {
System.out.println("Signature passed core validation");
}
} else {
System.out.println("Signature validation OK");
}

return result;
}

/**
* Write an XML document root node to a file
*
* @param doc XML Document
* @param outFile output filename
* @throws FileNotFoundException when input file is not found
* @throws TransformerException when an error occurred in the transformation
*/
public void writeToFile(Document doc, String outFile) throws IOException, TransformerException {

try (OutputStream os = new FileOutputStream(outFile)) {
TransformerFactory tf = getTransformerFactory();
Transformer trans = tf.newTransformer();
trans.transform(new DOMSource(doc), new StreamResult(os));
}
}
}

Note that we need the following dependencies.

<dependency>
<groupId>jakarta.activation</groupId>
<artifactId>jakarta.activation-api</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>${jaxb.version}</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>${jaxb.version}</version>
<scope>runtime</scope>
</dependency>

--

--

czetsuya

Open for Collaboration | Senior Java Backend Developer