The very first step involves parsing the provided XML string to generate a DOM (Document Object Model) representation. This structure enables programmatic manipulation of the XML data. Depending on your development environment, a variety of approaches are available. In JavaScript, the DOMParser API is ideal, while in Python, modules such as xml.dom.minidom allow similar functionality.
Below is an example in JavaScript which demonstrates how to create a DOM representation of our XML data.
// Define the XML data as a string
const xmlString = `<CsbReport ApplicationType="CSB_REPORT" ParticipantName="PT_400198">
<ReportListRequest ReportType="STTL_STATEMENT" MarketName="BALIMB" RunType="INDICATIVE"
StartDate="2024-06-09" EndDate="2024-06-09"/>
</CsbReport>`;
// Create a DOMParser instance
const parser = new DOMParser();
// Parse XML string into a DOM Document
const xmlDoc = parser.parseFromString(xmlString, "text/xml");
// The 'xmlDoc' object is now a DOM representation of the XML data
console.log(xmlDoc);
Note that using DOMParser, the XML is parsed to create a tree structure that can be navigated and manipulated.
For those using Python, the minidom module can be used similarly:
# Import the required module
from xml.dom import minidom
# Define the XML data
xml_data = """<CsbReport ApplicationType="CSB_REPORT" ParticipantName="PT_400198">
<ReportListRequest ReportType="STTL_STATEMENT" MarketName="BALIMB" RunType="INDICATIVE"
StartDate="2024-06-09" EndDate="2024-06-09"/>
</CsbReport>"""
# Parse the XML string to get a DOM representation
doc = minidom.parseString(xml_data)
# Output the DOM as an XML string for verification
print(doc.toxml())
Canonicalization (C14N) is the process of converting XML content into a standard, normalized format. This is crucial prior to performing any digital signature operations, as it ensures the quality and consistency of the XML structure regardless of minor formatting differences, such as whitespace or attribute ordering. In our requirement, we are following the method described by the XML C14N standard with comments (http://www.w3.org/TR/2001/REC-xmlc14n-20010315#WithComments
).
While some environments require specialized libraries for canonicalization (e.g., xmldom combined with a canonicalizer), a simplified approach using the XMLSerializer (in JavaScript) can be applied for basic canonicalization. Note that this simplified method might not handle every nuance of the official standard.
// Serialize the DOM representation back to string
const serializer = new XMLSerializer();
let canonicalXml = serializer.serializeToString(xmlDoc);
// Simplified approach: In practice, you might need a dedicated library to ensure that the XML
// adheres strictly to the canonicalization guidelines with comments.
console.log("Canonical XML:", canonicalXml);
Python developers can use the lxml library to perform canonicalization. This library provides support for strictly following the C14N method.
from lxml import etree
# Parse the original XML using lxml
root = etree.fromstring(xml_data)
# Canonicalize the XML with comments included (note: ensure the proper method or library is used)
canonicalized_xml = etree.tostring(root, encoding="unicode", method="xml")
# Normalize spacing by removing unnecessary newline or tab characters
canonicalized_xml = canonicalized_xml.replace('\n', '').replace('\t', '')
print("Canonicalized XML:", canonicalized_xml)
After canonicalization, the next step is to compute the SHA-256 digest of the canonicalized XML string. This digest serves as the input for the RSA signature process. Using SHA-256 ensures that the hash is robust and secure.
In modern applications, the Web Crypto API (for browsers) or Python’s Cryptography library can be employed to perform the digest and sign operations.
Although a fully functional implementation requires possessing a private key, below is a conceptual illustration:
async function generateRSASignature(canonicalXml, privateKey) {
// Convert the canonical XML into a Uint8Array for processing
const encoder = new TextEncoder();
const data = encoder.encode(canonicalXml);
// Compute the SHA-256 digest
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
// Create the signature by signing the hash with the RSA private key using RSASSA-PKCS1-v1_5
const signatureBuffer = await crypto.subtle.sign(
{
name: "RSASSA-PKCS1-v1_5",
hash: { name: "SHA-256" }
},
privateKey, // Your RSA private key must be imported as CryptoKey
hashBuffer
);
// Convert the signature from an ArrayBuffer into a base64-encoded string
const signatureArray = new Uint8Array(signatureBuffer);
let binaryString = "";
signatureArray.forEach(byte => binaryString += String.fromCharCode(byte));
const base64Signature = btoa(binaryString);
return base64Signature;
}
// Example usage: generateRSASignature(canonicalXml, myPrivateKey).then(console.log);
In Python, you can utilize the Cryptography library to generate the SHA-256 digest and sign it with an RSA key.
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
# Load the private key from a PEM file
with open("private_key.pem", "rb") as key_file:
private_key = serialization.load_pem_private_key(
key_file.read(),
password=None,
backend=default_backend()
)
# Compute the SHA-256 digest of the canonicalized XML
digest = hashes.Hash(hashes.SHA256(), backend=default_backend())
digest.update(canonicalized_xml.encode('utf-8'))
hashed_data = digest.finalize()
# Create the RSA signature using PSS padding
signature = private_key.sign(
hashed_data,
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA256()
)
The binary signature produced by the RSA encryption is not directly suitable for inclusion in XML documents. Encoding it to Base64 converts these bytes into a text string, which is safe for XML and other textual data formats, including SOAP messages.
The example code given in the signing step using the Web Crypto API already demonstrates converting the signature to Base64. In JavaScript, the use of btoa
achieves this conversion.
Python’s base64 module can perform this encoding:
import base64
# Convert the binary signature to a Base64-encoded string
base64_signature = base64.b64encode(signature).decode('utf-8')
print("Base64 Signature:", base64_signature)
In SOAP-based web services, the digital signature is typically embedded within the SOAP header. The Signature element, which encapsulates the Base64-encoded signature, becomes part of the security header.
A sample SOAP envelope including a Signature element is illustrated below.
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
<soap:Header>
<wsse:Security soap:mustUnderstand="true"
xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<ds:Signature Id="Signature-001" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod
Algorithm="http://www.w3.org/TR/2001/REC-xmlc14n-20010315#WithComments"/>
<ds:SignatureMethod
Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<ds:Reference URI="#CsbReport">
<ds:Transforms>
<ds:Transform
Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
</ds:Transforms>
<ds:DigestMethod
Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>[CalculatedDigestValue]</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>[Base64Signature]</ds:SignatureValue>
<ds:KeyInfo>
<ds:KeyValue>
<ds:RSAKeyValue>
<ds:Modulus>[ModulusValue]</ds:Modulus>
<ds:Exponent>[ExponentValue]</ds:Exponent>
</ds:RSAKeyValue>
</ds:KeyValue>
</ds:KeyInfo>
</ds:Signature>
</wsse:Security>
</soap:Header>
<soap:Body>
<CsbReport ApplicationType="CSB_REPORT" ParticipantName="PT_400198" id="CsbReport">
<ReportListRequest ReportType="STTL_STATEMENT" MarketName="BALIMB"
RunType="INDICATIVE" StartDate="2024-06-09" EndDate="2024-06-09"/>
</CsbReport>
</soap:Body>
</soap:Envelope>
Replace [Base64Signature]
with the generated Base64 signature string and fill in the digest and key details as required by your implementation and environment.
Once the XML has been generated and signed, it needs to be stored securely for further processing or audit. There are several methods for storing XML data:
If you are dealing with a browser-based application, you might store the XML in the local storage:
// Convert the DOM back to an XML string
const xmlDataToStore = serializer.serializeToString(xmlDoc);
// Store the XML data in the browser's localStorage
localStorage.setItem('csbReportXML', xmlDataToStore);
In a server-side environment using Node.js, you can write the XML data to a file:
const fs = require('fs');
// Define the SOAP message which includes the signature
const soapMessage = `<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
<soap:Header>...</soap:Header>
<soap:Body>
${xmlDataToStore}
</soap:Body>
</soap:Envelope>`;
// Write the SOAP message to an XML file
fs.writeFileSync("output.xml", soapMessage, "utf8");
Python also provides straightforward file handling capabilities:
# Define the SOAP message with the XML data
soap_message = f"""<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
<soap:Header>...</soap:Header>
<soap:Body>
{canonicalized_xml}
</soap:Body>
</soap:Envelope>"""
# Write the SOAP message to a file
with open("output.xml", "w", encoding="utf-8") as file:
file.write(soap_message)
Step | Description | Key Functions/Methods |
---|---|---|
DOM Creation | Generate a DOM from the XML string using parsing libraries. | DOMParser (JavaScript), minidom (Python) |
Canonicalization | Normalize the XML to a consistent format as per the XML C14N standard with comments. | XMLSerializer, lxml.etree.tostring() |
Digital Signature | Compute SHA-256 digest and sign it with the RSA private key. | crypto.subtle.digest/sign (JavaScript), Cryptography library (Python) |
Base64 Encoding | Convert the binary signature to a text string suitable for XML. | btoa() (JavaScript), base64.b64encode() (Python) |
SOAP Message | Embed the signature into a SOAP envelope for secure data interchange. | String interpolation, XML templating |
Data Storage | Store the XML data for future processing using file or database systems. | LocalStorage, fs.writeFileSync (Node.js), file write in Python |
In summary, the process of creating and signing an XML document involves several crucial stages. You begin by parsing the XML data to obtain a DOM representation, which allows for structured manipulation. This representation is then canonicalized to ensure the XML is in a normalized state, following the XML C14N with comments standard. Once canonicalization is complete, you digest the normalized XML using SHA-256 and then sign the digest using RSA encryption with the participant’s private key. The resulting binary signature must be encoded into Base64 to be safely embedded within a SOAP message. Finally, the signed SOAP message can be stored using various methods, depending on your system’s architecture. Each of these steps is foundational to achieving a secure, standard-compliant XML digital signature, ensuring both data integrity and authenticity in web services.