Java-Based Implementation of Digital Signatures and X509 Certificates for Secure XML Document Processing
https://www.czetsuyatech.com/2023/02/java-implementation-of-digital-signature-and-x509certificate.html
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.
- 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.
- 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.
- 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.
Table 1. Java Packages in the Java XML Digital Signature API
Package | Description |
---|---|
javax.xml.crypto | This package includes commonly used classes for executing cryptographic operations on XML. |
javax.xml.crypto.dom | This package comprises classes that are specific to the Document Object Model (DOM) for the javax.xml.crypto package. |
javax.xml.crypto.dsig | This package consists of classes that portray the fundamental elements specified in the XML digital signature standard. The most important class is XMLSignature, which permits signing and verifying an XML digital signature. The abstract factory class XMLSignatureFactory creates objects that implement these interfaces. |
javax.xml.crypto.dsig.dom | This package comprises classes that are specific to the Document Object Model (DOM) for the javax.xml.crypto.dsig package. |
javax.xml.crypto.dsig.keyinfo | This package includes classes that depict the KeyInfo structures specified in the XML digital signature recommendation. The abstract factory class KeyInfoFactory creates objects that implement these interfaces. |
javax.xml.crypto.dsig.spec | This package contains classes that represent the input parameters for various algorithms such as digest, signature, transform, or canonicalization, which are utilized in the processing of XML signatures. |
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>
For Sponsors
- Complete source code with unit tests https://github.com/java-czetsuyatech/xml-digital-signature
- The custom class X509KeySelector.
Post a Comment