Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

x509 Feat and Fix - fixes multiple wsse, add option do disable response verification, and use another cert for verify #1429

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
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)