Skip to content

Commit 718cf98

Browse files
committed
Fix signature verification for the redirect binding
- When an AuthnRequest is received by the Server - When a LogoutRequest is received by the client or the server
2 parents 5caf6da + a3b26f3 commit 718cf98

File tree

6 files changed

+285
-45
lines changed

6 files changed

+285
-45
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ venv.bak/
123123

124124
# PyCharm project files
125125
.idea/
126+
*.iml
126127

127128
# mkdocs documentation
128129
/site

src/saml2/client.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -630,7 +630,9 @@ def handle_logout_request(
630630
sign=None,
631631
sign_alg=None,
632632
digest_alg=None,
633-
relay_state="",
633+
relay_state=None,
634+
sigalg=None,
635+
signature=None,
634636
):
635637
"""
636638
Deal with a LogoutRequest
@@ -639,6 +641,11 @@ def handle_logout_request(
639641
:param name_id: The id of the current user
640642
:param binding: Which binding the message came in over
641643
:param sign: Whether the response will be signed or not
644+
:param sign_alg: The signing algorithm for the response
645+
:param digest_alg: The digest algorithm for the the response
646+
:param relay_state: The relay state of the request
647+
:param sigalg: The SigAlg query param of the request
648+
:param signature: The Signature query param of the request
642649
:return: Keyword arguments which can be used to send the response
643650
what's returned follow different patterns for different bindings.
644651
If the binding is BINDIND_SOAP, what is returned looks like this::
@@ -652,8 +659,13 @@ def handle_logout_request(
652659
"""
653660
logger.info("logout request: %s", request)
654661

655-
_req = self._parse_request(request, LogoutRequest,
656-
"single_logout_service", binding)
662+
_req = self.parse_logout_request(
663+
xmlstr=request,
664+
binding=binding,
665+
relay_state=relay_state,
666+
sigalg=sigalg,
667+
signature=signature,
668+
)
657669

658670
if _req.message.name_id == name_id:
659671
try:

src/saml2/entity.py

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1024,7 +1024,16 @@ def srv2typ(service):
10241024
else:
10251025
return typ
10261026

1027-
def _parse_request(self, enc_request, request_cls, service, binding):
1027+
def _parse_request(
1028+
self,
1029+
enc_request,
1030+
request_cls,
1031+
service,
1032+
binding,
1033+
relay_state=None,
1034+
sigalg=None,
1035+
signature=None,
1036+
):
10281037
"""Parse a Request
10291038
10301039
:param enc_request: The request in its transport format
@@ -1039,8 +1048,7 @@ def _parse_request(self, enc_request, request_cls, service, binding):
10391048
_log_debug = logger.debug
10401049

10411050
# The addresses I should receive messages like this on
1042-
receiver_addresses = self.config.endpoint(service, binding,
1043-
self.entity_type)
1051+
receiver_addresses = self.config.endpoint(service, binding, self.entity_type)
10441052
if not receiver_addresses and self.entity_type == "idp":
10451053
for typ in ["aa", "aq", "pdp"]:
10461054
receiver_addresses = self.config.endpoint(service, binding, typ)
@@ -1070,7 +1078,9 @@ def _parse_request(self, enc_request, request_cls, service, binding):
10701078
if only_valid_cert:
10711079
must = True
10721080
_request = _request.loads(xmlstr, binding, origdoc=enc_request,
1073-
must=must, only_valid_cert=only_valid_cert)
1081+
must=must, only_valid_cert=only_valid_cert,
1082+
relay_state=relay_state, sigalg=sigalg,
1083+
signature=signature)
10741084

10751085
_log_debug("Loaded request")
10761086

@@ -1574,7 +1584,14 @@ def parse_logout_request_response(self, xmlstr, binding=BINDING_SOAP):
15741584

15751585
# ------------------------------------------------------------------------
15761586

1577-
def parse_logout_request(self, xmlstr, binding=BINDING_SOAP):
1587+
def parse_logout_request(
1588+
self,
1589+
xmlstr,
1590+
binding=BINDING_SOAP,
1591+
relay_state=None,
1592+
sigalg=None,
1593+
signature=None,
1594+
):
15781595
""" Deal with a LogoutRequest
15791596
15801597
:param xmlstr: The response as a xml string
@@ -1584,8 +1601,15 @@ def parse_logout_request(self, xmlstr, binding=BINDING_SOAP):
15841601
was not.
15851602
"""
15861603

1587-
return self._parse_request(xmlstr, saml_request.LogoutRequest,
1588-
"single_logout_service", binding)
1604+
return self._parse_request(
1605+
enc_request=xmlstr,
1606+
request_cls=saml_request.LogoutRequest,
1607+
service="single_logout_service",
1608+
binding=binding,
1609+
relay_state=relay_state,
1610+
sigalg=sigalg,
1611+
signature=signature,
1612+
)
15891613

15901614
def use_artifact(self, message, endpoint_index=0):
15911615
"""

src/saml2/request.py

Lines changed: 71 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import logging
22

3+
from saml2 import time_util
4+
from saml2 import BINDING_HTTP_REDIRECT
5+
from saml2 import BINDING_HTTP_POST
36
from saml2.attribute_converter import to_local
4-
from saml2 import time_util, BINDING_HTTP_REDIRECT
57
from saml2.s_utils import OtherError
68

79
from saml2.validate import valid_instance
810
from saml2.validate import NotValid
911
from saml2.response import IncorrectlySigned
12+
from saml2.sigver import verify_redirect_signature
1013

1114
logger = logging.getLogger(__name__)
1215

@@ -36,38 +39,84 @@ def _clear(self):
3639
self.message = None
3740
self.not_on_or_after = 0
3841

39-
def _loads(self, xmldata, binding=None, origdoc=None, must=None,
40-
only_valid_cert=False):
41-
if binding == BINDING_HTTP_REDIRECT:
42-
pass
43-
42+
def _loads(
43+
self,
44+
xmldata,
45+
binding=None,
46+
origdoc=None,
47+
must=None,
48+
only_valid_cert=False,
49+
relay_state=None,
50+
sigalg=None,
51+
signature=None,
52+
):
4453
# own copy
4554
self.xmlstr = xmldata[:]
46-
logger.debug("xmlstr: %s", self.xmlstr)
55+
logger.debug("xmlstr: %s, relay_state: %s, sigalg: %s, signature: %s",
56+
self.xmlstr, relay_state, sigalg, signature)
57+
58+
signed_post = must and binding == BINDING_HTTP_POST
59+
signed_redirect = must and binding == BINDING_HTTP_REDIRECT
60+
incorrectly_signed = IncorrectlySigned("Request was not signed correctly")
61+
4762
try:
48-
self.message = self.signature_check(xmldata, origdoc=origdoc,
49-
must=must,
50-
only_valid_cert=only_valid_cert)
51-
except TypeError:
52-
raise
53-
except Exception as excp:
54-
logger.info("EXCEPTION: %s", excp)
63+
self.message = self.signature_check(
64+
xmldata,
65+
origdoc=origdoc,
66+
must=signed_post,
67+
only_valid_cert=only_valid_cert,
68+
)
69+
except Exception as e:
70+
self.message = None
71+
raise incorrectly_signed from e
72+
73+
if signed_redirect:
74+
if sigalg is None or signature is None:
75+
raise incorrectly_signed
76+
77+
_saml_msg = {
78+
"SAMLRequest": origdoc,
79+
"Signature": signature,
80+
"SigAlg": sigalg,
81+
}
82+
if relay_state is not None:
83+
_saml_msg["RelayState"] = relay_state
84+
try:
85+
sig_verified = self._do_redirect_sig_check(_saml_msg)
86+
except Exception as e:
87+
self.message = None
88+
raise incorrectly_signed from e
89+
else:
90+
if not sig_verified:
91+
self.message = None
92+
raise incorrectly_signed
5593

5694
if not self.message:
57-
logger.error("Response was not correctly signed")
58-
logger.info("Response: %s", xmldata)
59-
raise IncorrectlySigned()
95+
logger.error("Request was not signed correctly")
96+
logger.info("Request data: %s", xmldata)
97+
raise incorrectly_signed
6098

61-
logger.info("request: %s", self.message)
99+
logger.info("Request message: %s", self.message)
62100

63101
try:
64102
valid_instance(self.message)
65103
except NotValid as exc:
66-
logger.error("Not valid request: %s", exc.args[0])
104+
logger.error("Request not valid: %s", exc.args[0])
67105
raise
68106

69107
return self
70108

109+
def _do_redirect_sig_check(self, _saml_msg):
110+
issuer = self.message.issuer.text.strip()
111+
certs = self.sec.metadata.certs(issuer, "any", "signing")
112+
logger.debug("Certs to verify request sig: %s, _saml_msg: %s", certs, _saml_msg)
113+
verified = any(
114+
verify_redirect_signature(_saml_msg, self.sec.sec_backend, cert)
115+
for cert_name, cert in certs
116+
)
117+
logger.info("Redirect request signature check: %s", verified)
118+
return verified
119+
71120
def issue_instant_ok(self):
72121
""" Check that the request was issued at a reasonable time """
73122
upper = time_util.shift_time(time_util.time_in_a_while(days=1),
@@ -97,9 +146,10 @@ def _verify(self):
97146
return valid
98147

99148
def loads(self, xmldata, binding, origdoc=None, must=None,
100-
only_valid_cert=False):
149+
only_valid_cert=False, relay_state=None, sigalg=None, signature=None):
101150
return self._loads(xmldata, binding, origdoc, must,
102-
only_valid_cert=only_valid_cert)
151+
only_valid_cert=only_valid_cert, relay_state=relay_state,
152+
sigalg=sigalg, signature=signature)
103153

104154
def verify(self):
105155
try:

src/saml2/server.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,17 +226,23 @@ def verify_assertion_consumer_service(self, request):
226226

227227
# -------------------------------------------------------------------------
228228

229-
def parse_authn_request(self, enc_request, binding=BINDING_HTTP_REDIRECT):
229+
def parse_authn_request(self, enc_request, binding=BINDING_HTTP_REDIRECT,
230+
relay_state=None, sigalg=None, signature=None):
230231
"""Parse a Authentication Request
231232
232233
:param enc_request: The request in its transport format
233234
:param binding: Which binding that was used to transport the message
235+
:param relay_state: RelayState, when binding=redirect
236+
:param sigalg: Signature Algorithm, when binding=redirect
237+
:param signature: Signature, when binding=redirect
234238
to this entity.
235239
:return: A request instance
236240
"""
237241

238242
return self._parse_request(enc_request, AuthnRequest,
239-
"single_sign_on_service", binding)
243+
"single_sign_on_service", binding,
244+
relay_state=relay_state, sigalg=sigalg,
245+
signature=signature)
240246

241247
def parse_attribute_query(self, xml_string, binding):
242248
""" Parse an attribute query

0 commit comments

Comments
 (0)