Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 30 additions & 2 deletions signxml/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ def __init__(self, method=methods.enveloped, signature_algorithm="rsa-sha256", d

def sign(self, data, key=None, passphrase=None, cert=None, reference_uri=None, key_name=None, key_info=None,
id_attribute=None, always_add_key_value=False, payload_inclusive_ns_prefixes=frozenset(),
signature_inclusive_ns_prefixes=frozenset()):
signature_inclusive_ns_prefixes=frozenset(), signature_properties=None):
"""
Sign the data and return the root element of the resulting XML tree.

Expand Down Expand Up @@ -339,7 +339,11 @@ def sign(self, data, key=None, passphrase=None, cert=None, reference_uri=None, k
:param signature_inclusive_ns_prefixes:
Provide a list of XML namespace prefixes whose declarations should be preserved when canonicalizing the
signature itself (**InclusiveNamespaces PrefixList**).
:type inclusive_ns_prefixes: string
:type signature_inclusive_ns_prefixes: string
:param signature_properties:
One or more Elements that are to be included in the SignatureProperies section when using the detached
method.
:type signature_properties: :py:class:`lxml.etree.Element` or list of :py:class:`lxml.etree.Element`s

:returns:
A :py:class:`lxml.etree.Element` object representing the root of the XML tree containing the signature and
Expand All @@ -364,6 +368,14 @@ def sign(self, data, key=None, passphrase=None, cert=None, reference_uri=None, k
reference_uris = reference_uri

sig_root, doc_root, c14n_inputs, reference_uris = self._unpack(data, reference_uris)

if self.method == methods.detached and signature_properties is not None:
reference_uris.append("#prop")
if signature_properties is not None and not isinstance(signature_properties, list):
signature_properties = [signature_properties]
signature_properties_el = self._build_signature_properties(signature_properties)
c14n_inputs.append(signature_properties_el)

signed_info_element, signature_value_element = self._build_sig(sig_root, reference_uris, c14n_inputs,
sig_insp=signature_inclusive_ns_prefixes,
payload_insp=payload_inclusive_ns_prefixes)
Expand Down Expand Up @@ -428,6 +440,10 @@ def sign(self, data, key=None, passphrase=None, cert=None, reference_uri=None, k
if self.method == methods.enveloping:
for c14n_input in c14n_inputs:
doc_root.append(c14n_input)

if self.method == methods.detached and signature_properties is not None:
sig_root.append(signature_properties_el)

return doc_root if self.method == methods.enveloped else sig_root

def _get_c14n_inputs_from_reference_uris(self, doc_root, reference_uris):
Expand Down Expand Up @@ -519,6 +535,18 @@ def _build_sig(self, sig_root, reference_uris, c14n_inputs, sig_insp, payload_in
signature_value = SubElement(sig_root, ds_tag("SignatureValue"))
return signed_info, signature_value

def _build_signature_properties(self, signature_properties):
obj = Element(ds_tag("Object"), attrib={'Id': 'prop'}, nsmap={'ds': "http://www.w3.org/2000/09/xmldsig#"})
signature_properties_el = Element(ds_tag("SignatureProperties"))
for i, el in enumerate(signature_properties):
signature_property = Element(ds_tag("SignatureProperty"),
attrib={"Id": el.attrib.pop('Id', f"sigprop{i}"),
"Target": el.attrib.pop('Target', f"#sigproptarget{i}")})
signature_property.append(el)
signature_properties_el.append(signature_property)
obj.append(signature_properties_el)
return obj

def _serialize_key_value(self, key, key_info_element):
"""
Add the public components of the key to the signature (see https://www.w3.org/TR/xmldsig-core2/#sec-KeyValue).
Expand Down
17 changes: 17 additions & 0 deletions test/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -453,5 +453,22 @@ def test_soap_request_with_inclusive_namespaces(self):
expect_references=False,
validate_schema=False)

def test_signature_properties_with_detached_method(self):
doc = etree.Element('Test', attrib={'Id': 'mytest'})
sigprop = etree.Element('{http://somenamespace}MyCustomProperty')
sigprop.text = 'Some Text'
with open(os.path.join(os.path.dirname(__file__), "example.key"), "rb") as file:
key = file.read()
with open(os.path.join(os.path.dirname(__file__), "example.pem"), "rb") as file:
cert = file.read()
signature = XMLSigner(method=methods.detached).sign(doc,
cert=cert,
key=key,
reference_uri="#mytest",
signature_properties=sigprop)
fulldoc = b'<root>' + etree.tostring(signature) + etree.tostring(doc) + b'</root>'
XMLVerifier().verify(etree.fromstring(fulldoc), x509_cert=cert, expect_references=2)


if __name__ == '__main__':
unittest.main()