2025, Oct 31 10:02
ds प्रीफिक्स के बिना XML सिग्नेचर: SignXML क्यों विफल और xmlsec से समाधान
लेगेसी सिस्टम के लिए ds प्रीफिक्स रहित XML Digital Signature कैसे बनाएं व सत्यापित करें: SignXML में सत्यापन क्यों विफल, डिफ़ॉल्ट namespace संग xmlsec समाधान.
ds: प्रीफिक्स के बिना XML पर हस्ताक्षर: SignXML का सत्यापन क्यों विफल होता है और xmlsec के साथ इसे कैसे कार्यरत करें
कुछ पुराने सिस्टम यह अपेक्षा करते हैं कि XML Digital Signature तत्व सही namespace में हों, लेकिन ds: प्रीफिक्स न लगाया गया हो। यानी Signature और उसके सभी चाइल्ड टैग्स http://www.w3.org/2000/09/xmldsig# namespace में होने चाहिए, वह भी डिफ़ॉल्ट namespace के रूप में — पर टैग्स पर कोई स्पष्ट namespace प्रीफिक्स नहीं। SignXML में namespaces को ओवरराइड करके ऐसी सिग्नेचर बनाना आसान है, लेकिन सत्यापन विफल हो सकता है। नीचे एक व्यावहारिक चरण-दर-चरण उदाहरण है जो समस्या को दोहराता है और xmlsec का उपयोग करते हुए एक काम करने वाला विकल्प दिखाता है।
SignXML के साथ पुनरुत्पाद्य उदाहरण
निम्नलिखित कोड XML पर दो बार हस्ताक्षर करता है: पहली बार SignXML के डिफ़ॉल्ट व्यवहार के साथ (ds: मौजूद) और दूसरी बार डिफ़ॉल्ट namespace लागू करके (ds: हटा दिया गया)। दूसरी बार सत्यापन के दौरान ही असफलता आती है।
"""
Test Certificate Setup
Run the following commands to generate a self-signed cert and private key:
.. code-block:: bash
    openssl genrsa -out private.key 4096
    openssl req -new -key private.key -out request.csr
    openssl x509 -req -days 365 -in request.csr -signkey private.key -out certificate.crt
"""
from xml.etree.ElementTree import Element, SubElement, tostring
import lxml
import signxml
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
KEY_FILE = "/app/private.key"
CERT_FILE = "/app/certificate.crt"
def load_cert_and_pubkey(path: str):
    with open(path, "rb") as fh:
        raw = fh.read()
    cert = x509.load_pem_x509_certificate(raw, default_backend())
    return cert, cert.public_key()
def load_privkey(path: str, password=None):
    with open(path, "rb") as fh:
        raw = fh.read()
    return serialization.load_pem_private_key(raw, password=password, backend=default_backend())
def build_source_xml():
    root = Element("ROOT")
    data_node = SubElement(root, "DATA")
    id_node = SubElement(data_node, "ID")
    id_node.text = "encrypted_some_data_data"
    key_node = SubElement(data_node, "SESSION_KEY")
    key_node.text = "encrypted_session_key"
    return tostring(root, encoding="unicode")
private_key_obj = load_privkey(KEY_FILE)
cert_obj, pubkey_obj = load_cert_and_pubkey(CERT_FILE)
def sign_with_ds_prefix_and_verify():
    xml_payload = build_source_xml()
    doc = lxml.etree.fromstring(xml_payload.encode("utf-8"))
    signer = signxml.XMLSigner(
        method=signxml.methods.enveloped,
        signature_algorithm="rsa-sha256",
        digest_algorithm="sha256",
        c14n_algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315",
    )
    signed_doc = signer.sign(
        doc,
        key=private_key_obj,
        exclude_c14n_transform_element=True,
    )
    serialized = lxml.etree.tostring(signed_doc, encoding="utf-8")
    parsed = lxml.etree.fromstring(serialized)
    signxml.XMLVerifier().verify(parsed, x509_cert=cert_obj)
    # यहां सत्यापन सफल होता है।
def sign_without_ds_prefix_and_verify():
    xml_payload = build_source_xml()
    doc = lxml.etree.fromstring(xml_payload.encode("utf-8"))
    signer = signxml.XMLSigner(
        method=signxml.methods.enveloped,
        signature_algorithm="rsa-sha256",
        digest_algorithm="sha256",
        c14n_algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315",
    )
    # xmldsig को डिफ़ॉल्ट namespace के रूप में मजबूर करें, ताकि ds: प्रीफ़िक्स हट जाए
    signer.namespaces = {None: signxml.namespaces.ds}
    signed_doc = signer.sign(
        doc,
        key=private_key_obj,
        exclude_c14n_transform_element=True,
    )
    serialized = lxml.etree.tostring(signed_doc, encoding="utf-8")
    parsed = lxml.etree.fromstring(serialized)
    # इस परिस्थिति में यह कॉल एक exception उठाती है:
    # signxml.exceptions.InvalidInput: Expected to find XML element DigestMethod in {http://www.w3.org/2000/09/xmldsig#}Reference
    signxml.XMLVerifier().verify(parsed, x509_cert=cert_obj)
सत्यापन के दौरान असल में क्या टूटता है
सत्यापन केवल तब विफल होता है जब Digital Signature namespace को डिफ़ॉल्ट बनाकर ds: प्रीफिक्स हटा दिया जाता है। त्रुटि बताती है कि वेरिफ़ायर ds namespace के अंतर्गत DigestMethod ढूंढने की उम्मीद करता है। स्टैक ट्रेस से संकेत मिलता है कि खोज ds:DigestMethod के जरिए की जाती है, और तत्व सही namespace में होने के बावजूद, इस कॉन्फ़िगरेशन में लुकअप विफल हो जाता है।
signxml.exceptions.InvalidInput: Expected to find XML element DigestMethod in {http://www.w3.org/2000/09/xmldsig#}Reference
ds: प्रीफिक्स के साथ हस्ताक्षर करना उसी वातावरण में सफलतापूर्वक सत्यापित हो जाता है। वही दस्तावेज़ संरचना बिना प्रीफिक्स के सत्यापित नहीं होती।
एक कारगर विकल्प: ds: प्रीफिक्स के बिना xmlsec
SignXML से ds प्रीफिक्स के बिना साइन किया गया XML यहाँ सफलतापूर्वक सत्यापित नहीं हो पाया। पैकेज में हाल के अपडेट नहीं होने के कारण xmlsec पर स्विच करने से समस्या हल हो गई। xmlsec के साथ, ds प्रीफिक्स के बिना भी साइनिंग और वेरिफ़िकेशन दोनों अपेक्षा के मुताबिक काम करते हैं, और यह मौजूदा lxml वर्ज़न के साथ भी संगत है।
नीचे दिया गया उदाहरण वही XML बनाता है, उसे डिफ़ॉल्ट xmldsig namespace (बिना ds:) के साथ एनवेलप्ड सिग्नेचर से साइन करता है, और फिर सत्यापित करता है।
"""
Test Certificate Setup
Run the following commands to generate a self-signed cert and private key:
.. code-block:: bash
    openssl genrsa -out private.key 4096
    openssl req -new -key private.key -out request.csr
    openssl x509 -req -days 365 -in request.csr -signkey private.key -out certificate.crt
"""
import lxml.etree as LX
import xmlsec
from xml.etree.ElementTree import Element, SubElement, tostring
from cryptography import x509
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
PEM_KEY = "/app/tmp/private.key"
PEM_CERT = "/app/tmp/certificate.crt"
def read_pem_cert(p: str) -> bytes:
    with open(p, "rb") as f:
        return f.read()
def read_pem_key(p: str) -> bytes:
    with open(p, "rb") as f:
        return f.read()
def make_xml_payload() -> str:
    root = Element("ROOT")
    sec = SubElement(root, "DATA")
    rid = SubElement(sec, "ID")
    rid.text = "encrypted_some_data_data"
    skey = SubElement(sec, "SESSION_KEY")
    skey.text = "encrypted_session_key"
    return tostring(root, encoding="unicode")
priv_pem = read_pem_key(PEM_KEY)
cert_pem = read_pem_cert(PEM_CERT)
def sign_and_verify_with_xmlsec():
    xml_str = make_xml_payload()
    doc = LX.fromstring(xml_str.encode("utf-8"))
    # डिफ़ॉल्ट namespace (ns=None) के साथ signature टेम्पलेट बनाएं
    sig_node = xmlsec.template.create(
        doc,
        c14n_method=xmlsec.Transform.C14N,
        sign_method=xmlsec.Transform.RSA_SHA256,
        ns=None,
    )
    # डॉक्यूमेंट रूट के नीचे signature नोड जोड़ें
    doc.append(sig_node)
    # पूरे डॉक्यूमेंट को reference करें और enveloped ट्रांसफ़ॉर्म जोड़ें
    ref = xmlsec.template.add_reference(sig_node, xmlsec.Transform.SHA256, uri="")
    xmlsec.template.add_transform(ref, xmlsec.Transform.ENVELOPED)
    # हस्ताक्षर करना
    ctx = xmlsec.SignatureContext()
    ctx.key = xmlsec.Key.from_memory(priv_pem, xmlsec.KeyFormat.PEM, None)
    ctx.sign(sig_node)
    # दोबारा parse करें और सत्यापित करें
    reparsed = LX.fromstring(LX.tostring(doc))
    sig_for_verify = xmlsec.tree.find_node(reparsed, xmlsec.Node.SIGNATURE)
    vctx = xmlsec.SignatureContext()
    vctx.key = xmlsec.Key.from_memory(cert_pem, xmlsec.KeyFormat.CERT_PEM, None)
    vctx.verify(sig_for_verify)
यह व्यवहार क्यों मायने रखता है
लेगेसी सिस्टम के साथ एकीकरण में अक्सर ऐसे सख्त XML स्वरूप नियम होते हैं जो केवल स्कीमा की शुद्धता से आगे जाते हैं। तत्व सही namespace में होने पर भी ds: प्रीफिक्स की मौजूदगी या अनुपस्थिति इंटरऑपरेबिलिटी को प्रभावित कर सकती है। ऐसा टूलचेन चुनना जो xmldsig के लिए डिफ़ॉल्ट namespaces को स्वच्छ तरीके से सपोर्ट करे और उन हस्ताक्षरों का सत्यापन भी कर सके, अस्थायी उपायों से बचाता है और हैंडऑफ़ के दौरान जोखिम घटाता है।
व्यावहारिक निष्कर्ष
यदि लक्ष्य सिस्टम XML Digital Signature तत्वों को ds: प्रीफिक्स के बिना मांगता है, तो ध्यान रखें कि इस कॉन्फ़िगरेशन में SignXML का सत्यापन असफल हो सकता है। इसके विपरीत, xmlsec ऐसे दस्तावेज़ों पर भरोसेमंद तरीके से साइन और वेरिफ़ाई करता है और मौजूदा निर्भरताओं के साथ अच्छी तरह मेल खाता है। इन बाधाओं को ध्यान में रखने वाली लाइब्रेरी चुनना स्थिर इम्प्लीमेंटेशन तक का सबसे तेज़ रास्ता है।