From 07d2f9b1f7175fd566ccc3d5867ae18aff5698d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20W=C4=99grzynek?= Date: Wed, 17 Jan 2024 13:25:59 +0100 Subject: [PATCH] Support for signing non XML data --- signxml/processor.py | 33 ++++++++++++++++++++++----------- signxml/signer.py | 32 ++++++++++++++++++++++++-------- 2 files changed, 46 insertions(+), 19 deletions(-) diff --git a/signxml/processor.py b/signxml/processor.py index 011d588..0a54a72 100644 --- a/signxml/processor.py +++ b/signxml/processor.py @@ -36,10 +36,18 @@ def parser(self): return self._default_parser return self._parser - def _fromstring(self, xml_string, **kwargs): - xml_node = etree.fromstring(xml_string, parser=self.parser, **kwargs) - for entity in xml_node.iter(etree.Entity): - raise InvalidInput("Entities are not supported in XML input") + def _fromstring(self, data, **kwargs): + try: + xml_node = etree.fromstring(data, parser=self.parser, **kwargs) + for entity in xml_node.iter(etree.Entity): + raise InvalidInput("Entities are not supported in XML input") + except etree.XMLSyntaxError as e: + logger.debug("Data to be signed is not well-formed XML: %s", data) + if isinstance(data, str): + # We have to convert string to bytes + data = data.encode("utf-8") + return data + return xml_node def _tostring(self, xml_node, **kwargs): @@ -117,13 +125,16 @@ def _c14n(self, nodes, algorithm: CanonicalizationMethod, inclusive_ns_prefixes= c14n = b"" for node in nodes: - c14n += etree.tostring( - node, - method="c14n", - exclusive=exclusive, - with_comments=with_comments, - inclusive_ns_prefixes=inclusive_ns_prefixes, - ) + if isinstance(node, bytes): + c14n += node + else: + c14n += etree.tostring( + node, + method="c14n", + exclusive=exclusive, + with_comments=with_comments, + inclusive_ns_prefixes=inclusive_ns_prefixes, + ) if exclusive is False and self.excise_empty_xmlns_declarations is True: # Incorrect legacy behavior. See also: # - https://github.com/XML-Security/signxml/issues/193 diff --git a/signxml/signer.py b/signxml/signer.py index a56c19a..4f39023 100644 --- a/signxml/signer.py +++ b/signxml/signer.py @@ -324,6 +324,13 @@ def _get_c14n_inputs_from_references(self, doc_root, references: List[SignatureR def _unpack(self, data, references: List[SignatureReference]): sig_root = Element(ds_tag("Signature"), nsmap=self.namespaces) + if isinstance(data, str): + data = data.encode("utf-8") + data_is_not_xml = True + elif isinstance(data, bytes): + data_is_not_xml = True + else: + data_is_not_xml = False if self.construction_method == SignatureConstructionMethod.enveloped: if isinstance(data, (str, bytes)): raise InvalidInput("When using enveloped signature, **data** must be an XML element") @@ -357,11 +364,16 @@ def _unpack(self, data, references: List[SignatureReference]): elif self.construction_method == SignatureConstructionMethod.detached: doc_root = self.get_root(data) if references is None: + if data_is_not_xml: + raise InvalidInput("When using detached signature and data is not XML, **reference** has to be provided") uri = "#{}".format(data.get("Id", data.get("ID", "object"))) references = [SignatureReference(URI=uri)] c14n_inputs = [self.get_root(data)] - try: - c14n_inputs, references = self._get_c14n_inputs_from_references(doc_root, references) + try: + if data_is_not_xml: + c14n_inputs = [doc_root] + else: + c14n_inputs, references = self._get_c14n_inputs_from_references(doc_root, references) except InvalidInput: # Dummy reference URI c14n_inputs = [self.get_root(data)] elif self.construction_method == SignatureConstructionMethod.enveloping: @@ -400,13 +412,17 @@ def _build_sig(self, sig_root, references, c14n_inputs, inclusive_ns_prefixes): if reference.inclusive_ns_prefixes is None: reference = replace(reference, inclusive_ns_prefixes=inclusive_ns_prefixes) reference_node = SubElement(signed_info, ds_tag("Reference"), URI=reference.URI) - transforms = SubElement(reference_node, ds_tag("Transforms")) - self._build_transforms_for_reference(transforms_node=transforms, reference=reference) - SubElement(reference_node, ds_tag("DigestMethod"), Algorithm=self.digest_alg.value) + if not isinstance(c14n_inputs[i], bytes): + transforms = SubElement(reference_node, ds_tag("Transforms")) + self._build_transforms_for_reference(transforms_node=transforms, reference=reference) + SubElement(reference_node, ds_tag("DigestMethod"), Algorithm=self.digest_alg.value) digest_value = SubElement(reference_node, ds_tag("DigestValue")) - payload_c14n = self._c14n( - c14n_inputs[i], algorithm=reference.c14n_method, inclusive_ns_prefixes=reference.inclusive_ns_prefixes - ) + if not isinstance(c14n_inputs[i], bytes): + payload_c14n = self._c14n( + c14n_inputs[i], algorithm=reference.c14n_method, inclusive_ns_prefixes=reference.inclusive_ns_prefixes + ) + else: + payload_c14n = c14n_inputs[i] digest = self._get_digest(payload_c14n, algorithm=self.digest_alg) digest_value.text = b64encode(digest).decode() signature_value = SubElement(sig_root, ds_tag("SignatureValue"))