Skip to content

Commit 8f64f73

Browse files
apollo13mvantellingen
authored andcommitted
Allow customizing the digest and signature method during wsse signing. Fixes #616
1 parent bcd644d commit 8f64f73

File tree

2 files changed

+112
-24
lines changed

2 files changed

+112
-24
lines changed

src/zeep/wsse/signature.py

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,27 @@ def _make_verify_key(cert_data):
4545
class MemorySignature(object):
4646
"""Sign given SOAP envelope with WSSE sig using given key and cert."""
4747

48-
def __init__(self, key_data, cert_data, password=None):
48+
def __init__(
49+
self,
50+
key_data,
51+
cert_data,
52+
password=None,
53+
signature_method=None,
54+
digest_method=None,
55+
):
4956
check_xmlsec_import()
5057

5158
self.key_data = key_data
5259
self.cert_data = cert_data
5360
self.password = password
61+
self.digest_method = digest_method
62+
self.signature_method = signature_method
5463

5564
def apply(self, envelope, headers):
5665
key = _make_sign_key(self.key_data, self.cert_data, self.password)
57-
_sign_envelope_with_key(envelope, key)
66+
_sign_envelope_with_key(
67+
envelope, key, self.signature_method, self.digest_method
68+
)
5869
return envelope, headers
5970

6071
def verify(self, envelope):
@@ -66,9 +77,20 @@ def verify(self, envelope):
6677
class Signature(MemorySignature):
6778
"""Sign given SOAP envelope with WSSE sig using given key file and cert file."""
6879

69-
def __init__(self, key_file, certfile, password=None):
80+
def __init__(
81+
self,
82+
key_file,
83+
certfile,
84+
password=None,
85+
signature_method=None,
86+
digest_method=None,
87+
):
7088
super(Signature, self).__init__(
71-
_read_file(key_file), _read_file(certfile), password
89+
_read_file(key_file),
90+
_read_file(certfile),
91+
password,
92+
signature_method,
93+
digest_method,
7294
)
7395

7496

@@ -79,7 +101,9 @@ class BinarySignature(Signature):
79101

80102
def apply(self, envelope, headers):
81103
key = _make_sign_key(self.key_data, self.cert_data, self.password)
82-
_sign_envelope_with_key_binary(envelope, key)
104+
_sign_envelope_with_key_binary(
105+
envelope, key, self.signature_method, self.digest_method
106+
)
83107
return envelope, headers
84108

85109

@@ -92,7 +116,14 @@ def check_xmlsec_import():
92116
)
93117

94118

95-
def sign_envelope(envelope, keyfile, certfile, password=None):
119+
def sign_envelope(
120+
envelope,
121+
keyfile,
122+
certfile,
123+
password=None,
124+
signature_method=None,
125+
digest_method=None,
126+
):
96127
"""Sign given SOAP envelope with WSSE sig using given key and cert.
97128
98129
Sign the wsu:Timestamp node in the wsse:Security header and the soap:Body;
@@ -182,16 +213,18 @@ def sign_envelope(envelope, keyfile, certfile, password=None):
182213
"""
183214
# Load the signing key and certificate.
184215
key = _make_sign_key(_read_file(keyfile), _read_file(certfile), password)
185-
return _sign_envelope_with_key(envelope, key)
216+
return _sign_envelope_with_key(envelope, key, signature_method, digest_method)
186217

187218

188-
def _signature_prepare(envelope, key):
219+
def _signature_prepare(envelope, key, signature_method, digest_method):
189220
"""Prepare envelope and sign."""
190221
soap_env = detect_soap_env(envelope)
191222

192223
# Create the Signature node.
193224
signature = xmlsec.template.create(
194-
envelope, xmlsec.Transform.EXCL_C14N, xmlsec.Transform.RSA_SHA1
225+
envelope,
226+
xmlsec.Transform.EXCL_C14N,
227+
signature_method or xmlsec.Transform.RSA_SHA1,
195228
)
196229

197230
# Add a KeyInfo node with X509Data child to the Signature. XMLSec will fill
@@ -208,7 +241,7 @@ def _signature_prepare(envelope, key):
208241
# Perform the actual signing.
209242
ctx = xmlsec.SignatureContext()
210243
ctx.key = key
211-
_sign_node(ctx, signature, envelope.find(QName(soap_env, "Body")))
244+
_sign_node(ctx, signature, envelope.find(QName(soap_env, "Body")), digest_method)
212245
timestamp = security.find(QName(ns.WSU, "Timestamp"))
213246
if timestamp != None:
214247
_sign_node(ctx, signature, timestamp)
@@ -222,13 +255,17 @@ def _signature_prepare(envelope, key):
222255
return security, sec_token_ref, x509_data
223256

224257

225-
def _sign_envelope_with_key(envelope, key):
226-
_, sec_token_ref, x509_data = _signature_prepare(envelope, key)
258+
def _sign_envelope_with_key(envelope, key, signature_method, digest_method):
259+
_, sec_token_ref, x509_data = _signature_prepare(
260+
envelope, key, signature_method, digest_method
261+
)
227262
sec_token_ref.append(x509_data)
228263

229264

230-
def _sign_envelope_with_key_binary(envelope, key):
231-
security, sec_token_ref, x509_data = _signature_prepare(envelope, key)
265+
def _sign_envelope_with_key_binary(envelope, key, signature_method, digest_method):
266+
security, sec_token_ref, x509_data = _signature_prepare(
267+
envelope, key, signature_method, digest_method
268+
)
232269
ref = etree.SubElement(
233270
sec_token_ref,
234271
QName(ns.WSSE, "Reference"),
@@ -297,7 +334,7 @@ def _verify_envelope_with_key(envelope, key):
297334
raise SignatureVerificationFailed()
298335

299336

300-
def _sign_node(ctx, signature, target):
337+
def _sign_node(ctx, signature, target, digest_method=None):
301338
"""Add sig for ``target`` in ``signature`` node, using ``ctx`` context.
302339
303340
Doesn't actually perform the signing; ``ctx.sign(signature)`` should be
@@ -320,7 +357,7 @@ def _sign_node(ctx, signature, target):
320357

321358
# Add reference to signature with URI attribute pointing to that ID.
322359
ref = xmlsec.template.add_reference(
323-
signature, xmlsec.Transform.SHA1, uri="#" + node_id
360+
signature, digest_method or xmlsec.Transform.SHA1, uri="#" + node_id
324361
)
325362
# This is an XML normalization transform which will be performed on the
326363
# target node contents before signing. This ensures that changes to

tests/test_wsse_signature.py

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,30 @@
22
import sys
33

44
import pytest
5-
from lxml.etree import QName
65
from lxml import etree
6+
from lxml.etree import QName
7+
78
from tests.utils import load_xml
89
from zeep import ns, wsse
910
from zeep.exceptions import SignatureVerificationFailed
1011
from zeep.wsse import signature
1112
from zeep.wsse.signature import xmlsec as xmlsec_installed
1213

13-
DS_NS = "http://www.w3.org/2000/09/xmldsig#"
14-
15-
1614
KEY_FILE = os.path.join(os.path.dirname(os.path.realpath(__file__)), "cert_valid.pem")
1715
KEY_FILE_PW = os.path.join(
1816
os.path.dirname(os.path.realpath(__file__)), "cert_valid_pw.pem"
1917
)
2018

19+
SIGNATURE_METHODS_TESTDATA = (
20+
("RSA_SHA1", "http://www.w3.org/2000/09/xmldsig#rsa-sha1"),
21+
("RSA_SHA256", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"),
22+
)
23+
24+
DIGEST_METHODS_TESTDATA = (
25+
("SHA1", "http://www.w3.org/2000/09/xmldsig#sha1"),
26+
("SHA256", "http://www.w3.org/2001/04/xmlenc#sha256"),
27+
)
28+
2129

2230
skip_if_no_xmlsec = pytest.mark.skipif(
2331
sys.platform == "win32", reason="does not run on windows"
@@ -58,8 +66,15 @@ def test_sign_timestamp_if_present():
5866
signature.sign_envelope(envelope, KEY_FILE, KEY_FILE)
5967
signature.verify_envelope(envelope, KEY_FILE)
6068

69+
6170
@skip_if_no_xmlsec
62-
def test_sign():
71+
@pytest.mark.parametrize("digest_method,expected_digest_href", DIGEST_METHODS_TESTDATA)
72+
@pytest.mark.parametrize(
73+
"signature_method,expected_signature_href", SIGNATURE_METHODS_TESTDATA
74+
)
75+
def test_sign(
76+
digest_method, signature_method, expected_digest_href, expected_signature_href
77+
):
6378
envelope = load_xml(
6479
"""
6580
<soapenv:Envelope
@@ -77,9 +92,24 @@ def test_sign():
7792
"""
7893
)
7994

80-
signature.sign_envelope(envelope, KEY_FILE, KEY_FILE)
95+
signature.sign_envelope(
96+
envelope,
97+
KEY_FILE,
98+
KEY_FILE,
99+
signature_method=getattr(xmlsec_installed.Transform, signature_method),
100+
digest_method=getattr(xmlsec_installed.Transform, digest_method),
101+
)
81102
signature.verify_envelope(envelope, KEY_FILE)
82103

104+
digests = envelope.xpath("//ds:DigestMethod", namespaces={"ds": ns.DS})
105+
assert len(digests)
106+
for digest in digests:
107+
assert digest.get("Algorithm") == expected_digest_href
108+
signatures = envelope.xpath("//ds:SignatureMethod", namespaces={"ds": ns.DS})
109+
assert len(signatures)
110+
for sig in signatures:
111+
assert sig.get("Algorithm") == expected_signature_href
112+
83113

84114
@skip_if_no_xmlsec
85115
def test_sign_pw():
@@ -158,7 +188,13 @@ def test_signature():
158188

159189

160190
@pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows")
161-
def test_signature_binary():
191+
@pytest.mark.parametrize("digest_method,expected_digest_href", DIGEST_METHODS_TESTDATA)
192+
@pytest.mark.parametrize(
193+
"signature_method,expected_signature_href", SIGNATURE_METHODS_TESTDATA
194+
)
195+
def test_signature_binary(
196+
digest_method, signature_method, expected_digest_href, expected_signature_href
197+
):
162198
envelope = load_xml(
163199
"""
164200
<soapenv:Envelope
@@ -176,7 +212,13 @@ def test_signature_binary():
176212
"""
177213
)
178214

179-
plugin = wsse.BinarySignature(KEY_FILE_PW, KEY_FILE_PW, "geheim")
215+
plugin = wsse.BinarySignature(
216+
KEY_FILE_PW,
217+
KEY_FILE_PW,
218+
"geheim",
219+
signature_method=getattr(xmlsec_installed.Transform, signature_method),
220+
digest_method=getattr(xmlsec_installed.Transform, digest_method),
221+
)
180222
envelope, headers = plugin.apply(envelope, {})
181223
plugin.verify(envelope)
182224
# Test the reference
@@ -190,3 +232,12 @@ def test_signature_binary():
190232
namespaces={"soapenv": ns.SOAP_ENV_11, "wsse": ns.WSSE, "ds": ns.DS},
191233
)[0]
192234
assert "#" + bintok.attrib[QName(ns.WSU, "Id")] == ref.attrib["URI"]
235+
236+
digests = envelope.xpath("//ds:DigestMethod", namespaces={"ds": ns.DS})
237+
assert len(digests)
238+
for digest in digests:
239+
assert digest.get("Algorithm") == expected_digest_href
240+
signatures = envelope.xpath("//ds:SignatureMethod", namespaces={"ds": ns.DS})
241+
assert len(signatures)
242+
for sig in signatures:
243+
assert sig.get("Algorithm") == expected_signature_href

0 commit comments

Comments
 (0)