From 82ee848213c17e30f31517a6ba458418a6b58058 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans=20Ho=CC=88rberg?= Date: Mon, 3 Mar 2014 10:13:11 +0100 Subject: [PATCH 1/3] Only validate certificate and set client certificate tp authn request. Made it possible for the IdP to only validate the certificate without verifying the signature. This is needed when the proxy sends the SP certificate to the IdP. Made it possible to send the certificate that should be used during the creating of the authn request. --- .gitignore | 6 ++++++ src/saml2/client_base.py | 10 +++++++--- src/saml2/config.py | 1 + src/saml2/entity.py | 7 ++++++- src/saml2/md.py | 6 ++++++ src/saml2/metadata.py | 12 ++++++++++++ src/saml2/request.py | 8 ++++---- src/saml2/response.py | 7 ++++--- src/saml2/sigver.py | 22 +++++++++++++--------- 9 files changed, 59 insertions(+), 20 deletions(-) diff --git a/.gitignore b/.gitignore index 5c137b1..f3351b0 100644 --- a/.gitignore +++ b/.gitignore @@ -149,3 +149,9 @@ example/sp/sp_nocert2.xml example/sp/test.py example/sp/sp_conf.py + +example/sp/nocert_sp_conf/sp.xml + +example/sp/nocert_sp_conf/sp_conf.py + +example/sp/nocert_sp_conf/who.ini diff --git a/src/saml2/client_base.py b/src/saml2/client_base.py index 9e70933..fc21c566 100644 --- a/src/saml2/client_base.py +++ b/src/saml2/client_base.py @@ -231,7 +231,9 @@ class Base(Entity): :param kwargs: Extra key word arguments :return: instance """ - + client_crt = None + if "client_crt" in kwargs: + client_crt = kwargs["client_crt"] args = {} try: args["assertion_consumer_service_url"] = kwargs[ @@ -299,9 +301,11 @@ class Base(Entity): except KeyError: pass - if sign and self.sec.cert_handler.generate_cert(): + if (sign and self.sec.cert_handler.generate_cert()) or client_crt is not None: with self.lock: - self.sec.cert_handler.update_cert(True) + self.sec.cert_handler.update_cert(True, client_crt) + if client_crt is not None: + sign_prepare = True return self._message(AuthnRequest, destination, message_id, consent, extensions, sign, sign_prepare, protocol_binding=binding, diff --git a/src/saml2/config.py b/src/saml2/config.py index da98119..f79d0a7 100644 --- a/src/saml2/config.py +++ b/src/saml2/config.py @@ -92,6 +92,7 @@ SP_ARGS = [ AA_IDP_ARGS = [ "sign_assertion", "want_authn_requests_signed", + "want_authn_requests_only_with_valid_cert", "provided_attributes", "subject_data", "sp", diff --git a/src/saml2/entity.py b/src/saml2/entity.py index fe7d575..0465ad0 100644 --- a/src/saml2/entity.py +++ b/src/saml2/entity.py @@ -546,7 +546,12 @@ class Entity(HTTPBase): origdoc = xmlstr xmlstr = self.unravel(xmlstr, binding, request_cls.msgtype) must = self.config.getattr("want_authn_requests_signed", "idp") - _request = _request.loads(xmlstr, binding, origdoc=origdoc, must=must) + only_valid_cert = self.config.getattr("want_authn_requests_only_with_valid_cert", "idp") + if only_valid_cert is None: + only_valid_cert = False + if only_valid_cert: + must = True + _request = _request.loads(xmlstr, binding, origdoc=origdoc, must=must, only_valid_cert=only_valid_cert) _log_debug("Loaded request") diff --git a/src/saml2/md.py b/src/saml2/md.py index 81b2ef2..9b7bee7 100644 --- a/src/saml2/md.py +++ b/src/saml2/md.py @@ -1041,6 +1041,8 @@ class IDPSSODescriptorType_(SSODescriptorType_): c_cardinality['attribute'] = {"min": 0} c_attributes['WantAuthnRequestsSigned'] = ('want_authn_requests_signed', 'boolean', False) + c_attributes['WantAuthnRequestsOnlyWithValidCert'] = ('want_authn_requests_only_with_valid_cert', + 'boolean', False) c_child_order.extend(['single_sign_on_service', 'name_id_mapping_service', 'assertion_id_request_service', 'attribute_profile', 'attribute']) @@ -1069,6 +1071,7 @@ class IDPSSODescriptorType_(SSODescriptorType_): text=None, extension_elements=None, extension_attributes=None, + want_authn_requests_only_with_valid_cert=None, ): SSODescriptorType_.__init__(self, artifact_resolution_service=artifact_resolution_service, @@ -1095,6 +1098,7 @@ class IDPSSODescriptorType_(SSODescriptorType_): self.attribute_profile = attribute_profile or [] self.attribute = attribute or [] self.want_authn_requests_signed = want_authn_requests_signed + self.want_authn_requests_only_with_valid_cert = want_authn_requests_only_with_valid_cert def idpsso_descriptor_type__from_string(xml_string): @@ -2012,3 +2016,5 @@ ELEMENT_BY_TAG = { def factory(tag, **kwargs): return ELEMENT_BY_TAG[tag](**kwargs) + + diff --git a/src/saml2/metadata.py b/src/saml2/metadata.py index 1e3b6db..4c31b54 100644 --- a/src/saml2/metadata.py +++ b/src/saml2/metadata.py @@ -37,6 +37,7 @@ DEFAULTS = { "want_assertions_signed": "true", "authn_requests_signed": "false", "want_authn_requests_signed": "true", + "want_authn_requests_only_with_valid_cert": "false", } ORG_ATTR_TRANSL = { @@ -407,6 +408,7 @@ DEFAULT = { "want_assertions_signed": "true", "authn_requests_signed": "false", "want_authn_requests_signed": "false", + "want_authn_requests_only_with_valid_cert": "false", } @@ -527,6 +529,16 @@ def do_idpsso_descriptor(conf, cert=None): except KeyError: setattr(idpsso, key, DEFAULTS[key]) + for key in ["want_authn_requests_only_with_valid_cert"]: + try: + val = conf.getattr(key, "idp") + if val is None: + setattr(idpsso, key, DEFAULT["want_authn_requests_only_with_valid_cert"]) + else: + setattr(idpsso, key, "%s" % val) + except KeyError: + setattr(idpsso, key, DEFAULTS[key]) + return idpsso diff --git a/src/saml2/request.py b/src/saml2/request.py index c6a65cd..e834808 100644 --- a/src/saml2/request.py +++ b/src/saml2/request.py @@ -36,12 +36,12 @@ class Request(object): self.message = None self.not_on_or_after = 0 - def _loads(self, xmldata, binding=None, origdoc=None, must=None): + def _loads(self, xmldata, binding=None, origdoc=None, must=None, only_valid_cert=False): # own copy self.xmlstr = xmldata[:] logger.info("xmlstr: %s" % (self.xmlstr,)) try: - self.message = self.signature_check(xmldata, origdoc=origdoc, must=must) + self.message = self.signature_check(xmldata, origdoc=origdoc, must=must, only_valid_cert=only_valid_cert) except TypeError: raise except Exception, excp: @@ -84,8 +84,8 @@ class Request(object): assert self.issue_instant_ok() return self - def loads(self, xmldata, binding, origdoc=None, must=None): - return self._loads(xmldata, binding, origdoc, must) + def loads(self, xmldata, binding, origdoc=None, must=None, only_valid_cert=False): + return self._loads(xmldata, binding, origdoc, must, only_valid_cert=only_valid_cert) def verify(self): try: diff --git a/src/saml2/response.py b/src/saml2/response.py index 0a66ee5..1742c27 100644 --- a/src/saml2/response.py +++ b/src/saml2/response.py @@ -553,9 +553,10 @@ class AuthnResponse(StatusResponse): else: self.not_on_or_after = 0 - if not for_me(conditions, self.entity_id): - if not lax: - raise Exception("Not for me!!!") + if not self.allow_unsolicited: + if not for_me(conditions, self.entity_id): + if not lax: + raise Exception("Not for me!!!") if conditions.condition: # extra conditions for cond in conditions.condition: diff --git a/src/saml2/sigver.py b/src/saml2/sigver.py index 11ea524..8323de6 100644 --- a/src/saml2/sigver.py +++ b/src/saml2/sigver.py @@ -1006,9 +1006,13 @@ class CertHandler(object): def generate_cert(self): return self._generate_cert - def update_cert(self, active=False): - if self._generate_cert and active: - if self._cert_handler_extra_class is not None and self._cert_handler_extra_class.use_generate_cert_func(): + def update_cert(self, active=False, client_crt=None): + if (self._generate_cert and active) or client_crt is not None: + if client_crt is not None: + self._tmp_cert_str = client_crt + #No private key for signing + self._tmp_key_str = "" + elif self._cert_handler_extra_class is not None and self._cert_handler_extra_class.use_generate_cert_func(): (self._tmp_cert_str, self._tmp_key_str) = \ self._cert_handler_extra_class.generate_cert(self._cert_info, self._cert_str, self._key_str) else: @@ -1127,7 +1131,7 @@ class SecurityContext(object): ) def _check_signature(self, decoded_xml, item, node_name=NODE_NAME, - origdoc=None, id_attr="", must=False): + origdoc=None, id_attr="", must=False, only_valid_cert=False): #print item try: issuer = item.issuer.text.strip() @@ -1196,7 +1200,7 @@ class SecurityContext(object): logger.error("check_sig: %s" % exc) raise - if not verified: + if (not verified) and (not only_valid_cert): raise SignatureError("Failed to verify signature") else: if not self.cert_handler.verify_cert(last_pem_file): @@ -1211,7 +1215,7 @@ class SecurityContext(object): id_attr=id_attr, must=must) def correctly_signed_message(self, decoded_xml, msgtype, must=False, - origdoc=None): + origdoc=None, only_valid_cert=False): """Check if a request is correctly signed, if we have metadata for the entity that sent the info use that, if not use the key that are in the message if any. @@ -1239,12 +1243,12 @@ class SecurityContext(object): return msg return self._check_signature(decoded_xml, msg, class_name(msg), - origdoc, must=must) + origdoc, must=must, only_valid_cert=only_valid_cert) def correctly_signed_authn_request(self, decoded_xml, must=False, - origdoc=None): + origdoc=None, only_valid_cert=False): return self.correctly_signed_message(decoded_xml, "authn_request", - must, origdoc) + must, origdoc, only_valid_cert=only_valid_cert) def correctly_signed_authn_query(self, decoded_xml, must=False, origdoc=None): From d4b3f16e5b9ba387aaa46f6931d135c0bb4613c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans=20Ho=CC=88rberg?= Date: Tue, 4 Mar 2014 14:26:23 +0100 Subject: [PATCH 2/3] Removed imports. --- src/saml2/cert.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/saml2/cert.py b/src/saml2/cert.py index d561986..8234daa 100644 --- a/src/saml2/cert.py +++ b/src/saml2/cert.py @@ -1,11 +1,9 @@ +__author__ = 'haho0032' + import base64 -import traceback -from M2Crypto.util import passphrase_callback import datetime import dateutil.parser import pytz - -__author__ = 'haho0032' from OpenSSL import crypto from os.path import exists, join from os import remove From 7edc2447a5bfc78d8ba0095312e7c2dc0494691f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans=20Ho=CC=88rberg?= Date: Tue, 4 Mar 2014 14:31:58 +0100 Subject: [PATCH 3/3] Added pytz --- example/sp/who.ini | 1 + setup.py | 1 + 2 files changed, 2 insertions(+) diff --git a/example/sp/who.ini b/example/sp/who.ini index ae65a67..1ed329f 100644 --- a/example/sp/who.ini +++ b/example/sp/who.ini @@ -17,6 +17,7 @@ saml_conf = sp_conf remember_name = auth_tkt sid_store = outstanding idp_query_param = IdPEntityId +discovery = http://130.239.201.5/role/idp.ds [general] request_classifier = s2repoze.plugins.challenge_decider:my_request_classifier diff --git a/setup.py b/setup.py index 67e09cd..ea10b24 100755 --- a/setup.py +++ b/setup.py @@ -44,6 +44,7 @@ install_requires = [ 'zope.interface', 'repoze.who', 'pycrypto', #'Crypto' + 'pytz' ] tests_require = [