Skip to content
3 changes: 2 additions & 1 deletion CONTRIBUTORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,6 @@ Contributors
* Raymond Piller
* Zoltan Benedek
* Øyvind Heddeland Instefjord
* Gil Obradors
* Pol Sanlorenzo

* Caio Salgado
15 changes: 10 additions & 5 deletions docs/wsse.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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::
Expand All @@ -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

Expand Down
9 changes: 7 additions & 2 deletions src/zeep/wsdl/bindings/soap.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from collections.abc import Sequence
import logging
import typing

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
21 changes: 17 additions & 4 deletions src/zeep/wsse/signature.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
module.

"""

from lxml import etree
from lxml.etree import QName

Expand Down Expand Up @@ -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()

Expand All @@ -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)
Expand All @@ -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

Expand All @@ -84,13 +93,17 @@ def __init__(
password=None,
signature_method=None,
digest_method=None,
verify_reply_signature=True,
response_certfile=None,
):
super().__init__(
_read_file(key_file),
_read_file(certfile),
password,
signature_method,
digest_method,
verify_reply_signature,
_read_file(response_certfile) if response_certfile else None,
)


Expand Down Expand Up @@ -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
Expand All @@ -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)