XML-Security / XML-Security/signxml

Unable to sign/verify signed SOAP documents

Open
#133 9 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
154
Forks
113
PR merge metrics
No merged PRs in 30d

Description

We have a java and c# application that signs xml documents for inter process communication, as well as sending the documents outside of our network to a 3rd party vendor. I am trying to reproduce the same functionality in python, with limited success.

Test xml

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
    <tns:Body id="Body" xmlns:tns="http://schemas.xmlsoap.org/soap/envelope/">test</tns:Body>
</soap:Envelope>

Both the java/net version code behaves in a similar manner(pseudo code below):

xml = loadxml("file.xml")
soap-header =  new element("Header", "http://schemas.xmlsoap.org/soap/envelope/")
soap-signature = new element("Signature", "http://schemas.xmlsoap.org/soap/security/2000-12")
soap-header.append(soap-signature)
xml.insert(0, soap-header)
signer = new signer(soap-header) #IMPORTANT
signer.addreference("#Body")
signer.canonmethod = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"
signer.digest = SHA1
signer.signature = RSA-SHA1
signature = signer.sign(x509cert, password)
soap-signature.append(signature)

The document now looks like(without the body/envelop tag):

        <SOAP-SEC:Signature>
            <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
                <ds:SignedInfo>
                    <ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
                    <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
                    <ds:Reference URI="#Body">
                        <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
                        <ds:DigestValue>digest is here</ds:DigestValue>
                    </ds:Reference>
                </ds:SignedInfo>
                <ds:SignatureValue>base64 sig here</ds:SignatureValue>
                <ds:KeyInfo>
                    <ds:X509Data>
                        <ds:X509IssuerSerial>
                            <ds:X509IssuerName>cert name</ds:X509IssuerName>
                            <ds:X509SerialNumber>cert serial</ds:X509SerialNumber>
                        </ds:X509IssuerSerial>
                    </ds:X509Data>
                </ds:KeyInfo>
            </ds:Signature>
        </SOAP-SEC:Signature>
    </SOAP:Header>

A soap header/signature is added to the document, the body is signed, and the resulting signature is added to the document. Java/c# and our vendors system all agree on the signatures and all validate successfully.
Nothing I sign in python will validate in c#/java, and nothing I sign in c#/java will validate in python.

I think the core of the issue is the signer in c#/java takes a signing context(the soap header instead of the root of the document). Doing this new signer(soap-header) vs new signer(xml) changes both the body digest and signature, as the canonical form changes do to the context scoping. I am unclear if this is vendor specific, or if this is a requirement of the SOAP security extension, as it relates to this line in the specification:

Note that XML Canonicalization [XML-C14N] of ds:SignedInfo and other signed resources MUST each be done within its own context. This means, among other things, that the Canonical form [XML-C14N] of ds:SigndInfo always inherits the namespace declarations for SOAP-ENV and SOAP-SEC

Unless I am just missing it or have a fundamental misunderstanding of the spec(probably the latter), I need to provide a context to the signature and I see know way to do that in the library.

Here is my python code:

cert_bytes = open("cert.pfx", "rb").read()
(private_key, certificate, additional_certificates) = pkcs12.load_key_and_certificates(data=cert_bytes, password="password".encode(), backend=default_backend())
data = open("file.xml"", encoding='UTF-8').read()
root = ElementTree.fromstring(data)
soap_header = ElementTree.Element("SOAP:Header", attrib={"xmlns:SOAP": "http://schemas.xmlsoap.org/soap/envelope/", "xmlns:SOAP-SEC": "http://schemas.xmlsoap.org/soap/security/2000-12"})
soap_security = ElementTree.SubElement(soap_header, "SOAP-SEC:Signature")
root.insert(0, soap_header)
body = root.find("{http://schemas.xmlsoap.org/soap/envelope/}Body")
signer = signxml.XMLSigner(method=signxml.methods.detached,
                           signature_algorithm='rsa-sha1',
                           digest_algorithm='sha1',
                           c14n_algorithm='http://www.w3.org/TR/2001/REC-xml-c14n-20010315')
signed_info = signer.sign(data=body,
                          key=private_key,
                          cert=[certificate],
                          reference_uri="#Body")
signed_info_xml = (ElementTree.tostring(signed_info).decode())
signature = ElementTree.fromstring(signed_info_xml)
soap_security.append(signature)
ElementTree.ElementTree(root).write(file_or_filename="signed.xml", encoding="utf-8", method="xml")
signed_document = ElementTree.fromstring(open("signed.xml").read())
signature = signed_document.find('{http://www.w3.org/2000/09/xmldsig#}Signature')
verify_cert = open("publickey.pem").read()
verified_data = signxml.XMLVerifier().verify(x509_cert=verify_cert, data=signature).signed_xml

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at signxml.XMLSigner.sign and the XMLVerifier().verify call, using the sample SOAP document and the Java/C# signing flow as the interoperability cases. Investigate how the signing context and canonicalization are represented, then confirm that Python signatures validate in the other implementations and vice versa.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.