Skip to content

Commit ec3f598

Browse files
committed
Correctly sign an AuthnRequest with Redirect binding
When an AuthnRequest is created with HTTP-Redirect binding, the XML document is not signed, but instead, a signature is calculated and becomes part of the query params of the Redirect-URL, through the Signature and SignAlg params. Previously, when the Redirect binding was requested and signing was enabled but no SignAlg params were defined, the Signature and SignAlg query params would be missing. Now, if no SignAlg is defined, the default is used and the request is correctly created with the proper query params. Signed-off-by: Ivan Kanakarakis <[email protected]>
1 parent 5cdd5c4 commit ec3f598

File tree

4 files changed

+60
-14
lines changed

4 files changed

+60
-14
lines changed

src/saml2/client.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ def prepare_for_authenticate(
4949
scoping=None,
5050
consent=None, extensions=None,
5151
sign=None,
52+
sigalg=None,
5253
response_binding=saml2.BINDING_HTTP_POST,
5354
**kwargs,
5455
):
@@ -79,6 +80,7 @@ def prepare_for_authenticate(
7980
consent=consent,
8081
extensions=extensions,
8182
sign=sign,
83+
sigalg=sigalg,
8284
response_binding=response_binding,
8385
**kwargs,
8486
)
@@ -104,6 +106,7 @@ def prepare_for_negotiated_authenticate(
104106
extensions=None,
105107
sign=None,
106108
response_binding=saml2.BINDING_HTTP_POST,
109+
sigalg=None,
107110
**kwargs,
108111
):
109112
""" Makes all necessary preparations for an authentication request
@@ -133,6 +136,13 @@ def prepare_for_negotiated_authenticate(
133136
destination = self._sso_location(entityid, binding)
134137
logger.info("destination to provider: %s", destination)
135138

139+
# XXX - sign_post will embed the signature to the xml doc
140+
# XXX ^through self.create_authn_request(...)
141+
# XXX - sign_redirect will add the signature to the query params
142+
# XXX ^through self.apply_binding(...)
143+
sign_post = (binding == BINDING_HTTP_POST and sign)
144+
sign_redirect = (binding == BINDING_HTTP_REDIRECT and sign)
145+
136146
reqid, request = self.create_authn_request(
137147
destination,
138148
vorg,
@@ -141,25 +151,21 @@ def prepare_for_negotiated_authenticate(
141151
nameid_format,
142152
consent=consent,
143153
extensions=extensions,
144-
sign=sign,
154+
sign=sign_post,
155+
sign_alg=sigalg,
145156
**kwargs,
146157
)
147158

148159
_req_str = str(request)
149160
logger.info("AuthNReq: %s", _req_str)
150161

151-
try:
152-
args = {'sigalg': kwargs["sigalg"]}
153-
except KeyError:
154-
args = {}
155-
156162
http_info = self.apply_binding(
157163
binding,
158164
_req_str,
159165
destination,
160166
relay_state,
161-
sign=sign,
162-
**args,
167+
sign=sign_redirect,
168+
sigalg=sigalg,
163169
)
164170

165171
return reqid, binding, http_info
@@ -258,11 +264,16 @@ def do_logout(self, name_id, entity_ids, reason, expire, sign=None,
258264
if sign is None:
259265
sign = self.logout_requests_signed
260266

261-
sigalg = None
267+
def_sig = ds.DefaultSignature()
268+
sign_alg = def_sig.get_sign_alg() if sign_alg is None else sign_alg
269+
digest_alg = (
270+
def_sig.get_digest_alg()
271+
if digest_alg is None
272+
else digest_alg
273+
)
274+
262275
if sign:
263276
if binding == BINDING_HTTP_REDIRECT:
264-
sigalg = kwargs.get("sigalg", ds.DefaultSignature().get_sign_alg())
265-
# key = kwargs.get("key", self.signkey)
266277
srequest = str(request)
267278
else:
268279
srequest = self.sign(
@@ -279,7 +290,7 @@ def do_logout(self, name_id, entity_ids, reason, expire, sign=None,
279290
destination,
280291
relay_state,
281292
sign=sign,
282-
sigalg=sigalg,
293+
sigalg=sign_alg,
283294
)
284295

285296
if binding == BINDING_SOAP:

src/saml2/client_base.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@
5454
from saml2 import BINDING_HTTP_POST
5555
from saml2 import BINDING_PAOS
5656

57+
import saml2.xmldsig as ds
58+
5759

5860
logger = logging.getLogger(__name__)
5961

@@ -444,7 +446,13 @@ def create_authn_request(
444446

445447
client_crt = kwargs.get("client_crt")
446448
nsprefix = kwargs.get("nsprefix")
449+
450+
# XXX will be used to embed the signature to the xml doc - ie, POST binding
451+
# XXX always called by the SP, no need to check the context
447452
sign = self.authn_requests_signed if sign is None else sign
453+
def_sig = ds.DefaultSignature()
454+
sign_alg = sign_alg or def_sig.get_sign_alg()
455+
digest_alg = digest_alg or def_sig.get_digest_alg()
448456

449457
if (sign and self.sec.cert_handler.generate_cert()) or client_crt is not None:
450458
with self.lock:

src/saml2/entity.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@
7272
from saml2.sigver import signed_instance_factory
7373
from saml2.virtual_org import VirtualOrg
7474

75+
import saml2.xmldsig as ds
76+
77+
7578
logger = logging.getLogger(__name__)
7679

7780
__author__ = 'rolandh'
@@ -198,6 +201,7 @@ def apply_binding(
198201
relay_state="",
199202
response=False,
200203
sign=None,
204+
sigalg=None,
201205
**kwargs,
202206
):
203207
"""
@@ -212,6 +216,22 @@ def apply_binding(
212216
:param kwargs: response type specific arguments
213217
:return: A dictionary
214218
"""
219+
220+
# XXX authn_requests_signed (obj property) applies only to an SP
221+
# XXX sign_response (config option) applies to idp/aa
222+
# XXX Looking into sp/idp/aa properties should be done in the same way
223+
# XXX ^this discrepancy should be fixed
224+
sign_config = (
225+
self.authn_requests_signed
226+
if self.config.context == "sp"
227+
else self.config.getattr("sign_response")
228+
if self.config.context == "idp"
229+
else None
230+
)
231+
sign = sign_config if sign is None else sign
232+
def_sig = ds.DefaultSignature()
233+
sigalg = sigalg or def_sig.get_sign_alg()
234+
215235
# unless if BINDING_HTTP_ARTIFACT
216236
if response:
217237
typ = "SAMLResponse"
@@ -231,7 +251,6 @@ def apply_binding(
231251
info["method"] = "POST"
232252
elif binding == BINDING_HTTP_REDIRECT:
233253
logger.info("HTTP REDIRECT")
234-
sigalg = kwargs.get("sigalg")
235254
signer = (
236255
self.sec.sec_backend.get_signer(sigalg)
237256
if sign and sigalg
@@ -243,12 +262,15 @@ def apply_binding(
243262
relay_state,
244263
typ,
245264
signer=signer,
265+
sigalg=sigalg,
246266
**kwargs,
247267
)
248268
info["url"] = str(destination)
249269
info["method"] = "GET"
250270
elif binding == BINDING_SOAP or binding == BINDING_PAOS:
251-
info = self.use_soap(msg_str, destination, sign=sign, **kwargs)
271+
info = self.use_soap(
272+
msg_str, destination, sign=sign, sigalg=sigalg, **kwargs
273+
)
252274
elif binding == BINDING_URI:
253275
info = self.use_http_uri(msg_str, typ, destination)
254276
elif binding == BINDING_HTTP_ARTIFACT:

src/saml2/pack.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,12 @@ def http_redirect_message(message, location, relay_state="", typ="SAMLRequest",
178178
if relay_state:
179179
args["RelayState"] = relay_state
180180

181+
# XXX !should not depend on signer, but on sign
182+
# XXX if both signalg and signer are here they have to match
183+
# XXX now we allow them to differ
184+
# XXX signer should be created here; not passed in
181185
if signer:
186+
# XXX check for allowed algo -- should do the same for POST binding
182187
# sigalgs, should be one defined in xmldsig
183188
if sigalg not in [long_name for short_name, long_name in SIG_ALLOWED_ALG]:
184189
raise Exception(

0 commit comments

Comments
 (0)