diff --git a/CONTRIBUTORS.rst b/CONTRIBUTORS.rst index 1f02e449..8b88dd51 100644 --- a/CONTRIBUTORS.rst +++ b/CONTRIBUTORS.rst @@ -50,5 +50,6 @@ Contributors * Raymond Piller * Zoltan Benedek * Øyvind Heddeland Instefjord +* Gil Obradors * Pol Sanlorenzo - +* Caio Salgado diff --git a/docs/wsse.rst b/docs/wsse.rst index f1c528ef..564beec0 100644 --- a/docs/wsse.rst +++ b/docs/wsse.rst @@ -10,7 +10,7 @@ The UsernameToken supports both the passwordText and passwordDigest methods:: >>> from zeep import Client >>> from zeep.wsse.username import UsernameToken >>> client = Client( - ... 'http://www.webservicex.net/ConvertSpeed.asmx?WSDL', + ... 'http://www.webservicex.net/ConvertSpeed.asmx?WSDL', ... wsse=UsernameToken('username', 'password')) To use the passwordDigest method you need to supply `use_digest=True` to the @@ -21,19 +21,24 @@ Signature (x509) ---------------- To use the wsse.Signature() plugin you will need to install the `xmlsec`_ -module. See the `README`_ for xmlsec for the required dependencies on your +module. See the `README`_ for xmlsec for the required dependencies on your platform. To append the security token as `BinarySecurityToken`, you can use wsse.BinarySignature() plugin. +To skip response signature verification set `verify_reply_signature=False` + +To configure different certificate for response verify process, set `response_key_file` or +and `response_certfile`. + Example usage A:: >>> from zeep import Client >>> from zeep.wsse.signature import Signature >>> client = Client( - ... 'http://www.webservicex.net/ConvertSpeed.asmx?WSDL', + ... 'http://www.webservicex.net/ConvertSpeed.asmx?WSDL', ... wsse=Signature( - ... private_key_filename, public_key_filename, + ... private_key_filename, public_key_filename, ... optional_password)) Example usage B:: @@ -48,7 +53,7 @@ Example usage B:: >>> client = Client( ... 'http://www.webservicex.net/ConvertSpeed.asmx?WSDL', ... transport=transport) - + .. _xmlsec: https://pypi.python.org/pypi/xmlsec .. _README: https://github.com/mehcode/python-xmlsec diff --git a/src/zeep/wsdl/bindings/soap.py b/src/zeep/wsdl/bindings/soap.py index 3a5e5433..bf08ce8c 100644 --- a/src/zeep/wsdl/bindings/soap.py +++ b/src/zeep/wsdl/bindings/soap.py @@ -1,3 +1,4 @@ +from collections.abc import Sequence import logging import typing @@ -93,7 +94,7 @@ def _create(self, operation, args, kwargs, client=None, options=None): # Apply WSSE if client.wsse: - if isinstance(client.wsse, list): + if isinstance(client.wsse, Sequence): for wsse in client.wsse: envelope, http_headers = wsse.apply(envelope, http_headers) else: @@ -216,7 +217,11 @@ def process_reply(self, client, operation, response): message_pack = None if client.wsse: - client.wsse.verify(doc) + if isinstance(client.wsse, Sequence): + for wsse in client.wsse: + wsse.verify(doc) + else: + client.wsse.verify(doc) doc, http_headers = plugins.apply_ingress( client, doc, response.headers, operation diff --git a/src/zeep/wsse/signature.py b/src/zeep/wsse/signature.py index c4aec758..3e1a6247 100644 --- a/src/zeep/wsse/signature.py +++ b/src/zeep/wsse/signature.py @@ -8,6 +8,7 @@ module. """ + from lxml import etree from lxml.etree import QName @@ -52,6 +53,8 @@ def __init__( password=None, signature_method=None, digest_method=None, + verify_reply_signature=True, + response_cert_data=None, ): check_xmlsec_import() @@ -60,6 +63,8 @@ def __init__( self.password = password self.digest_method = digest_method self.signature_method = signature_method + self.verify_reply_signature = verify_reply_signature + self.response_cert_data = response_cert_data def apply(self, envelope, headers): key = _make_sign_key(self.key_data, self.cert_data, self.password) @@ -69,7 +74,11 @@ def apply(self, envelope, headers): return envelope, headers def verify(self, envelope): - key = _make_verify_key(self.cert_data) + if not self.verify_reply_signature: + return envelope + key = _make_verify_key( + self.cert_data if not self.response_cert_data else self.response_cert_data + ) _verify_envelope_with_key(envelope, key) return envelope @@ -84,6 +93,8 @@ def __init__( password=None, signature_method=None, digest_method=None, + verify_reply_signature=True, + response_certfile=None, ): super().__init__( _read_file(key_file), @@ -91,6 +102,8 @@ def __init__( password, signature_method, digest_method, + verify_reply_signature, + _read_file(response_certfile) if response_certfile else None, ) @@ -347,7 +360,7 @@ def _sign_node(ctx, signature, target, digest_method=None): """ # Ensure the target node has a wsu:Id attribute and get its value. - node_id = ensure_id(target) + ensure_id(target) # Unlike HTML, XML doesn't have a single standardized Id. WSSE suggests the # use of the wsu:Id attribute for this purpose, but XMLSec doesn't @@ -357,10 +370,10 @@ def _sign_node(ctx, signature, target, digest_method=None): # Add reference to signature with URI attribute pointing to that ID. ref = xmlsec.template.add_reference( - signature, digest_method or xmlsec.Transform.SHA1, uri="#" + node_id + signature, digest_method or xmlsec.Transform.SHA1, uri="" ) # This is an XML normalization transform which will be performed on the # target node contents before signing. This ensures that changes to # irrelevant whitespace, attribute ordering, etc won't invalidate the # signature. - xmlsec.template.add_transform(ref, xmlsec.Transform.EXCL_C14N) + xmlsec.template.add_transform(ref, xmlsec.Transform.ENVELOPED)