Skip to content

Commit 53df90f

Browse files
author
Roland Hedberg
committed
Merge pull request #96 from HaToHo/master
Added EncryptedAssertion with signature for the response.
2 parents 8b823d6 + 8c5ce87 commit 53df90f

File tree

16 files changed

+243
-110
lines changed

16 files changed

+243
-110
lines changed

.gitignore

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,3 +155,17 @@ example/sp/nocert_sp_conf/sp.xml
155155
example/sp/nocert_sp_conf/sp_conf.py
156156

157157
example/sp/nocert_sp_conf/who.ini
158+
159+
example/sp-repoze/my_sp.xml
160+
161+
example/sp-repoze/pki/localhost.ca.crt
162+
163+
example/sp-repoze/pki/localhost.ca.key
164+
165+
example/sp-repoze/sp.xml
166+
167+
example/sp-repoze/sp.xml
168+
169+
example/sp-repoze/sp_conf.py
170+
171+
example/sp-repoze/sp_conf.py

example/idp2/idp.py

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/env python
22
import argparse
33
import base64
4-
4+
import xmldsig as ds
55
import re
66
import logging
77
import time
@@ -24,6 +24,7 @@
2424
from saml2.authn_context import PASSWORD
2525
from saml2.authn_context import UNSPECIFIED
2626
from saml2.authn_context import authn_context_class_ref
27+
from saml2.extension import pefim
2728
from saml2.httputil import Response
2829
from saml2.httputil import NotFound
2930
from saml2.httputil import geturl
@@ -38,7 +39,7 @@
3839
from saml2.s_utils import UnknownPrincipal
3940
from saml2.s_utils import UnsupportedBinding
4041
from saml2.s_utils import PolicyError
41-
from saml2.sigver import verify_redirect_signature
42+
from saml2.sigver import verify_redirect_signature, cert_from_instance, encrypt_cert_from_item
4243

4344
logger = logging.getLogger("saml2.idp")
4445

@@ -125,8 +126,9 @@ def operation(self, _dict, binding):
125126
return resp(self.environ, self.start_response)
126127
else:
127128
try:
129+
_encrypt_cert = encrypt_cert_from_item(_dict["req_info"].message)
128130
return self.do(_dict["SAMLRequest"], binding,
129-
_dict["RelayState"])
131+
_dict["RelayState"], encrypt_cert=_encrypt_cert)
130132
except KeyError:
131133
# Can live with no relay state
132134
return self.do(_dict["SAMLRequest"], binding)
@@ -151,7 +153,7 @@ def response(self, binding, http_args):
151153
resp = Response(http_args["data"], headers=http_args["headers"])
152154
return resp(self.environ, self.start_response)
153155

154-
def do(self, query, binding, relay_state=""):
156+
def do(self, query, binding, relay_state="", encrypt_cert=None):
155157
pass
156158

157159
def redirect(self):
@@ -277,7 +279,7 @@ def verify_request(self, query, binding):
277279

278280
return resp_args, _resp
279281

280-
def do(self, query, binding_in, relay_state=""):
282+
def do(self, query, binding_in, relay_state="", encrypt_cert=None):
281283
try:
282284
resp_args, _resp = self.verify_request(query, binding_in)
283285
except UnknownPrincipal, excp:
@@ -297,13 +299,10 @@ def do(self, query, binding_in, relay_state=""):
297299
if REPOZE_ID_EQUIVALENT:
298300
identity[REPOZE_ID_EQUIVALENT] = self.user
299301
try:
300-
sign_assertion = IDP.config.getattr("sign_assertion", "idp")
301-
if sign_assertion is None:
302-
sign_assertion = False
303302
_resp = IDP.create_authn_response(
304303
identity, userid=self.user,
305-
authn=AUTHN_BROKER[self.environ["idp.authn_ref"]], sign_assertion=sign_assertion,
306-
sign_response=False, **resp_args)
304+
authn=AUTHN_BROKER[self.environ["idp.authn_ref"]], encrypt_cert=encrypt_cert,
305+
**resp_args)
307306
except Exception, excp:
308307
logging.error(exception_trace(excp))
309308
resp = ServiceError("Exception: %s" % (excp,))
@@ -537,7 +536,7 @@ def not_found(environ, start_response):
537536
# return subject, sp_entity_id
538537

539538
class SLO(Service):
540-
def do(self, request, binding, relay_state=""):
539+
def do(self, request, binding, relay_state="", encrypt_cert=None):
541540
logger.info("--- Single Log Out Service ---")
542541
try:
543542
_, body = request.split("\n")
@@ -589,7 +588,7 @@ def do(self, request, binding, relay_state=""):
589588

590589
class NMI(Service):
591590

592-
def do(self, query, binding, relay_state=""):
591+
def do(self, query, binding, relay_state="", encrypt_cert=None):
593592
logger.info("--- Manage Name ID Service ---")
594593
req = IDP.parse_manage_name_id_request(query, binding)
595594
request = req.message
@@ -617,7 +616,7 @@ def do(self, query, binding, relay_state=""):
617616

618617
# Only URI binding
619618
class AIDR(Service):
620-
def do(self, aid, binding, relay_state=""):
619+
def do(self, aid, binding, relay_state="", encrypt_cert=None):
621620
logger.info("--- Assertion ID Service ---")
622621

623622
try:
@@ -646,7 +645,7 @@ def operation(self, _dict, binding, **kwargs):
646645
# ----------------------------------------------------------------------------
647646

648647
class ARS(Service):
649-
def do(self, request, binding, relay_state=""):
648+
def do(self, request, binding, relay_state="", encrypt_cert=None):
650649
_req = IDP.parse_artifact_resolve(request, binding)
651650

652651
msg = IDP.create_artifact_response(_req, _req.artifact.text)
@@ -664,7 +663,7 @@ def do(self, request, binding, relay_state=""):
664663

665664
# Only SOAP binding
666665
class AQS(Service):
667-
def do(self, request, binding, relay_state=""):
666+
def do(self, request, binding, relay_state="", encrypt_cert=None):
668667
logger.info("--- Authn Query Service ---")
669668
_req = IDP.parse_authn_query(request, binding)
670669
_query = _req.message
@@ -688,7 +687,7 @@ def do(self, request, binding, relay_state=""):
688687

689688
# Only SOAP binding
690689
class ATTR(Service):
691-
def do(self, request, binding, relay_state=""):
690+
def do(self, request, binding, relay_state="", encrypt_cert=None):
692691
logger.info("--- Attribute Query Service ---")
693692

694693
_req = IDP.parse_attribute_query(request, binding)
@@ -721,7 +720,7 @@ def do(self, request, binding, relay_state=""):
721720

722721

723722
class NIM(Service):
724-
def do(self, query, binding, relay_state=""):
723+
def do(self, query, binding, relay_state="", encrypt_cert=None):
725724
req = IDP.parse_name_id_mapping_request(query, binding)
726725
request = req.message
727726
# Do the necessary stuff

example/sp-repoze/sp.xml

Lines changed: 0 additions & 34 deletions
This file was deleted.

example/sp-repoze/sp_conf.py renamed to example/sp-repoze/sp_conf.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,4 @@
4848
},
4949
"loglevel": "debug",
5050
}
51-
}
51+
}

src/s2repoze/plugins/sp.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import traceback
2828
import saml2
2929
from urlparse import parse_qs, urlparse
30+
from saml2.md import Extensions
31+
import xmldsig as ds
3032

3133
from StringIO import StringIO
3234

@@ -35,14 +37,15 @@
3537
from paste.httpexceptions import HTTPInternalServerError
3638
from paste.request import parse_dict_querystring
3739
from paste.request import construct_url
40+
from saml2.extension.pefim import SPCertEnc
3841
from saml2.httputil import SeeOther
3942
from saml2.client_base import ECP_SERVICE
4043
from zope.interface import implements
4144

4245
from repoze.who.interfaces import IChallenger, IIdentifier, IAuthenticator
4346
from repoze.who.interfaces import IMetadataProvider
4447

45-
from saml2 import ecp, BINDING_HTTP_REDIRECT
48+
from saml2 import ecp, BINDING_HTTP_REDIRECT, element_to_extension_element
4649
from saml2 import BINDING_HTTP_POST
4750

4851
from saml2.client import Saml2Client
@@ -126,7 +129,7 @@ class SAML2Plugin(object):
126129
implements(IChallenger, IIdentifier, IAuthenticator, IMetadataProvider)
127130

128131
def __init__(self, rememberer_name, config, saml_client, wayf, cache,
129-
sid_store=None, discovery="", idp_query_param=""):
132+
sid_store=None, discovery="", idp_query_param="", sid_store_cert=None,):
130133
self.rememberer_name = rememberer_name
131134
self.wayf = wayf
132135
self.saml_client = saml_client
@@ -143,6 +146,11 @@ def __init__(self, rememberer_name, config, saml_client, wayf, cache,
143146
self.outstanding_queries = shelve.open(sid_store, writeback=True)
144147
else:
145148
self.outstanding_queries = {}
149+
if sid_store_cert:
150+
self.outstanding_certs = shelve.open(sid_store_cert, writeback=True)
151+
else:
152+
self.outstanding_certs = {}
153+
146154
self.iam = platform.node()
147155

148156

@@ -362,15 +370,30 @@ def challenge(self, environ, _status, _app_headers, _forget_headers):
362370
dest = srvs[0]["location"]
363371
logger.debug("destination: %s" % dest)
364372

373+
extensions = None
374+
cert = None
375+
376+
if _cli.config.generate_cert_func is not None:
377+
cert_str, req_key_str = _cli.config.generate_cert_func()
378+
cert = {
379+
"cert": cert_str,
380+
"key": req_key_str
381+
}
382+
spcertenc = SPCertEnc(x509_data=ds.X509Data(x509_certificate=ds.X509Certificate(text=cert_str)))
383+
extensions = Extensions(extension_elements=[element_to_extension_element(spcertenc)])
384+
365385
if _cli.authn_requests_signed:
366386
_sid = saml2.s_utils.sid(_cli.seed)
367387
msg_str = _cli.create_authn_request(dest, vorg=vorg_name, sign=_cli.authn_requests_signed,
368-
message_id=_sid)
388+
message_id=_sid, extensions=extensions)
369389
else:
370-
req = _cli.create_authn_request(dest, vorg=vorg_name, sign=False)
390+
req = _cli.create_authn_request(dest, vorg=vorg_name, sign=False, extensions=extensions)
371391
msg_str = "%s" % req
372392
_sid = req.id
373393

394+
if cert is not None:
395+
self.outstanding_certs[_sid] = cert
396+
374397
ht_args = _cli.apply_binding(_binding, msg_str, destination=dest, relay_state=came_from)
375398

376399
logger.debug("ht_args: %s" % ht_args)
@@ -417,7 +440,8 @@ def _eval_authn_response(self, environ, post, binding=BINDING_HTTP_POST):
417440
# Evaluate the response, returns a AuthnResponse instance
418441
try:
419442
authresp = self.saml_client.parse_authn_request_response(
420-
post["SAMLResponse"], binding, self.outstanding_queries)
443+
post["SAMLResponse"], binding, self.outstanding_queries, self.outstanding_certs)
444+
421445
except Exception, excp:
422446
logger.exception("Exception: %s" % (excp,))
423447
raise

src/saml2/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,8 @@ def to_string(self, nspair=None):
558558
except AttributeError:
559559
# Backwards compatibility with ET < 1.3
560560
ElementTree._namespace_map[uri] = prefix
561+
except ValueError:
562+
pass
561563

562564
return ElementTree.tostring(self._to_element_tree(), encoding="UTF-8")
563565

src/saml2/client_base.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,9 @@ def __init__(self, config=None, identity_cache=None, state_cache=None,
122122
self.allow_unsolicited = False
123123
self.authn_requests_signed = False
124124
self.want_assertions_signed = False
125+
self.want_response_signed = False
125126
for foo in ["allow_unsolicited", "authn_requests_signed",
126-
"logout_requests_signed", "want_assertions_signed"]:
127+
"logout_requests_signed", "want_assertions_signed", "want_response_signed"]:
127128
v = self.config.getattr(foo, "sp")
128129
if v is True or v == 'true':
129130
setattr(self, foo, True)
@@ -234,7 +235,9 @@ def create_authn_request(self, destination, vorg="", scoping=None,
234235
client_crt = None
235236
if "client_crt" in kwargs:
236237
client_crt = kwargs["client_crt"]
238+
237239
args = {}
240+
238241
try:
239242
args["assertion_consumer_service_url"] = kwargs[
240243
"assertion_consumer_service_urls"][0]
@@ -505,7 +508,7 @@ def create_name_id_mapping_request(self, name_id_policy,
505508

506509
# ======== response handling ===========
507510

508-
def parse_authn_request_response(self, xmlstr, binding, outstanding=None):
511+
def parse_authn_request_response(self, xmlstr, binding, outstanding=None, outstanding_certs=None):
509512
""" Deal with an AuthnResponse
510513
511514
:param xmlstr: The reply as a xml string
@@ -525,8 +528,10 @@ def parse_authn_request_response(self, xmlstr, binding, outstanding=None):
525528
if xmlstr:
526529
kwargs = {
527530
"outstanding_queries": outstanding,
531+
"outstanding_certs": outstanding_certs,
528532
"allow_unsolicited": self.allow_unsolicited,
529533
"want_assertions_signed": self.want_assertions_signed,
534+
"want_response_signed": self.want_response_signed,
530535
"return_addrs": self.service_urls(),
531536
"entity_id": self.config.entityid,
532537
"attribute_converters": self.config.attribute_converters,

src/saml2/config.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,9 @@
6565
"xmlsec_path",
6666
"extension_schemas",
6767
"cert_handler_extra_class",
68+
"generate_cert_func",
6869
"generate_cert_info",
70+
"verify_encrypt_cert",
6971
"tmp_cert_file",
7072
"tmp_key_file",
7173
"validate_certificate",
@@ -78,6 +80,7 @@
7880
"idp",
7981
"aa",
8082
"subject_data",
83+
"want_response_signed",
8184
"want_assertions_signed",
8285
"authn_requests_signed",
8386
"name_form",
@@ -92,6 +95,8 @@
9295

9396
AA_IDP_ARGS = [
9497
"sign_assertion",
98+
"sign_response",
99+
"encrypt_assertion",
95100
"want_authn_requests_signed",
96101
"want_authn_requests_only_with_valid_cert",
97102
"provided_attributes",
@@ -210,6 +215,8 @@ def __init__(self, homedir="."):
210215
self.allow_unknown_attributes = False
211216
self.extension_schema = {}
212217
self.cert_handler_extra_class = None
218+
self.verify_encrypt_cert = None
219+
self.generate_cert_func = None
213220
self.generate_cert_info = None
214221
self.tmp_cert_file = None
215222
self.tmp_key_file = None

0 commit comments

Comments
 (0)