Fixes for EncryptedAssertion and signing.

This commit is contained in:
Hans Hörberg
2014-03-19 15:16:07 +01:00
parent 495d5f683b
commit e43f63cae3
9 changed files with 47 additions and 25 deletions

View File

@@ -301,7 +301,7 @@ class SSO(Service):
try: try:
_resp = IDP.create_authn_response( _resp = IDP.create_authn_response(
identity, userid=self.user, identity, userid=self.user,
authn=AUTHN_BROKER[self.environ["idp.authn_ref"]], sign_response=False, encrypt_cert=encrypt_cert, authn=AUTHN_BROKER[self.environ["idp.authn_ref"]], encrypt_cert=encrypt_cert,
**resp_args) **resp_args)
except Exception, excp: except Exception, excp:
logging.error(exception_trace(excp)) logging.error(exception_trace(excp))

View File

@@ -78,6 +78,7 @@ CONFIG = {
#Information needed for generated cert (NO CERT) solution. #Information needed for generated cert (NO CERT) solution.
"authn_requests_signed": "true", #Will sign the request! "authn_requests_signed": "true", #Will sign the request!
"want_assertions_signed": "false", #Demands that the assertion is signed. "want_assertions_signed": "false", #Demands that the assertion is signed.
"want_response_signed": "true",
"allow_unsolicited": "true", #Allows the message not to be ment for this sp. "allow_unsolicited": "true", #Allows the message not to be ment for this sp.
############################################################# #############################################################
"name": "LocalTestSPHans", "name": "LocalTestSPHans",

View File

@@ -558,6 +558,8 @@ class SamlBase(ExtensionContainer):
except AttributeError: except AttributeError:
# Backwards compatibility with ET < 1.3 # Backwards compatibility with ET < 1.3
ElementTree._namespace_map[uri] = prefix ElementTree._namespace_map[uri] = prefix
except ValueError:
pass
return ElementTree.tostring(self._to_element_tree(), encoding="UTF-8") return ElementTree.tostring(self._to_element_tree(), encoding="UTF-8")

View File

@@ -122,8 +122,9 @@ class Base(Entity):
self.allow_unsolicited = False self.allow_unsolicited = False
self.authn_requests_signed = False self.authn_requests_signed = False
self.want_assertions_signed = False self.want_assertions_signed = False
self.want_response_signed = False
for foo in ["allow_unsolicited", "authn_requests_signed", for foo in ["allow_unsolicited", "authn_requests_signed",
"logout_requests_signed", "want_assertions_signed"]: "logout_requests_signed", "want_assertions_signed", "want_response_signed"]:
v = self.config.getattr(foo, "sp") v = self.config.getattr(foo, "sp")
if v is True or v == 'true': if v is True or v == 'true':
setattr(self, foo, True) setattr(self, foo, True)
@@ -530,6 +531,7 @@ class Base(Entity):
"outstanding_certs": outstanding_certs, "outstanding_certs": outstanding_certs,
"allow_unsolicited": self.allow_unsolicited, "allow_unsolicited": self.allow_unsolicited,
"want_assertions_signed": self.want_assertions_signed, "want_assertions_signed": self.want_assertions_signed,
"want_response_signed": self.want_response_signed,
"return_addrs": self.service_urls(), "return_addrs": self.service_urls(),
"entity_id": self.config.entityid, "entity_id": self.config.entityid,
"attribute_converters": self.config.attribute_converters, "attribute_converters": self.config.attribute_converters,

View File

@@ -80,6 +80,7 @@ SP_ARGS = [
"idp", "idp",
"aa", "aa",
"subject_data", "subject_data",
"want_response_signed",
"want_assertions_signed", "want_assertions_signed",
"authn_requests_signed", "authn_requests_signed",
"name_form", "name_form",

View File

@@ -460,10 +460,16 @@ class Entity(HTTPBase):
return signed_instance_factory(response, self.sec, to_sign) return signed_instance_factory(response, self.sec, to_sign)
if encrypt_assertion: if encrypt_assertion:
sign_class = [(class_name(response), response.id)]
if sign:
response.signature = pre_signature_part(response.id, self.sec.my_cert, 1)
cbxs = CryptoBackendXmlSec1(self.config.xmlsec_binary) cbxs = CryptoBackendXmlSec1(self.config.xmlsec_binary)
_, cert_file = make_temp("%s" % encrypt_cert, decode=False) _, cert_file = make_temp("%s" % encrypt_cert, decode=False)
return cbxs.encrypt_assertion(response, cert_file, pre_encryption_part())#template(response.assertion.id)) response = cbxs.encrypt_assertion(response, cert_file, pre_encryption_part())#template(response.assertion.id))
#response = response_from_string(response_str) if sign:
return signed_instance_factory(response, self.sec, sign_class)
else:
return response
if sign: if sign:
return self.sign(response, to_sign=to_sign) return self.sign(response, to_sign=to_sign)
@@ -811,16 +817,18 @@ class Entity(HTTPBase):
raise raise
xmlstr = self.unravel(xmlstr, binding, response_cls.msgtype) xmlstr = self.unravel(xmlstr, binding, response_cls.msgtype)
origxml = xmlstr
if outstanding_certs is not None: if outstanding_certs is not None:
_response = samlp.any_response_from_string(xmlstr) _response = samlp.any_response_from_string(xmlstr)
_, cert_file = make_temp("%s" % outstanding_certs[_response.in_response_to]["key"], decode=False) if len(_response.encrypted_assertion) > 0:
cbxs = CryptoBackendXmlSec1(self.config.xmlsec_binary) _, cert_file = make_temp("%s" % outstanding_certs[_response.in_response_to]["key"], decode=False)
xmlstr = cbxs.decrypt(xmlstr, cert_file) cbxs = CryptoBackendXmlSec1(self.config.xmlsec_binary)
xmlstr = cbxs.decrypt(xmlstr, cert_file)
if not xmlstr: # Not a valid reponse if not xmlstr: # Not a valid reponse
return None return None
try: try:
response = response.loads(xmlstr, False) response = response.loads(xmlstr, False, origxml=origxml)
except SigverError, err: except SigverError, err:
logger.error("Signature Error: %s" % err) logger.error("Signature Error: %s" % err)
return None return None
@@ -831,6 +839,13 @@ class Entity(HTTPBase):
logger.debug("XMLSTR: %s" % xmlstr) logger.debug("XMLSTR: %s" % xmlstr)
for encrypted_assertion in response.response.encrypted_assertion:
if encrypted_assertion.extension_elements is not None:
assertion_list = extension_elements_to_elements(encrypted_assertion.extension_elements, [saml])
for assertion in assertion_list:
_assertion = saml.assertion_from_string(str(assertion))
response.response.assertion.append(_assertion)
if response: if response:
response = response.verify() response = response.verify()

View File

@@ -268,6 +268,7 @@ class StatusResponse(object):
self.in_response_to = None self.in_response_to = None
self.signature_check = self.sec.correctly_signed_response self.signature_check = self.sec.correctly_signed_response
self.require_signature = False self.require_signature = False
self.require_response_signature = False
self.not_signed = False self.not_signed = False
self.asynchop = asynchop self.asynchop = asynchop
@@ -318,7 +319,9 @@ class StatusResponse(object):
logger.debug("xmlstr: %s" % (self.xmlstr,)) logger.debug("xmlstr: %s" % (self.xmlstr,))
try: try:
self.response = self.signature_check(xmldata, origdoc=origxml, must=self.require_signature) self.response = self.signature_check(xmldata, origdoc=origxml, must=self.require_signature,
require_response_signature=self.require_response_signature)
except TypeError: except TypeError:
raise raise
except SignatureError: except SignatureError:
@@ -452,7 +455,7 @@ class AuthnResponse(StatusResponse):
return_addrs=None, outstanding_queries=None, return_addrs=None, outstanding_queries=None,
timeslack=0, asynchop=True, allow_unsolicited=False, timeslack=0, asynchop=True, allow_unsolicited=False,
test=False, allow_unknown_attributes=False, test=False, allow_unknown_attributes=False,
want_assertions_signed=False, **kwargs): want_assertions_signed=False, want_response_signed=False, **kwargs):
StatusResponse.__init__(self, sec_context, return_addrs, timeslack, StatusResponse.__init__(self, sec_context, return_addrs, timeslack,
asynchop=asynchop) asynchop=asynchop)
@@ -469,6 +472,7 @@ class AuthnResponse(StatusResponse):
self.session_not_on_or_after = 0 self.session_not_on_or_after = 0
self.allow_unsolicited = allow_unsolicited self.allow_unsolicited = allow_unsolicited
self.require_signature = want_assertions_signed self.require_signature = want_assertions_signed
self.require_response_signature = want_response_signed
self.test = test self.test = test
self.allow_unknown_attributes = allow_unknown_attributes self.allow_unknown_attributes = allow_unknown_attributes
# #

View File

@@ -427,7 +427,8 @@ class Server(Entity):
def create_authn_response(self, identity, in_response_to, destination, def create_authn_response(self, identity, in_response_to, destination,
sp_entity_id, name_id_policy=None, userid=None, sp_entity_id, name_id_policy=None, userid=None,
name_id=None, authn=None, issuer=None, name_id=None, authn=None, issuer=None,
sign_response=False, sign_assertion=None, encrypt_cert=None, **kwargs): sign_response=None, sign_assertion=None, encrypt_cert=None, encrypt_assertion=None,
**kwargs):
""" Constructs an AuthenticationResponse """ Constructs an AuthenticationResponse
:param identity: Information about an user :param identity: Information about an user
@@ -465,7 +466,11 @@ class Server(Entity):
if sign_response is None: if sign_response is None:
sign_response = False sign_response = False
encrypt_assertion = self.config.getattr("encrypt_assertion", "idp") if encrypt_assertion is None:
encrypt_assertion = self.config.getattr("encrypt_assertion", "idp")
if encrypt_assertion is None:
encrypt_assertion = False
if encrypt_assertion: if encrypt_assertion:
if encrypt_cert is not None: if encrypt_cert is not None:
verify_encrypt_cert = self.config.getattr("verify_encrypt_cert", "idp") verify_encrypt_cert = self.config.getattr("verify_encrypt_cert", "idp")

View File

@@ -1474,7 +1474,7 @@ class SecurityContext(object):
return self.correctly_signed_message(decoded_xml, "assertion", must, return self.correctly_signed_message(decoded_xml, "assertion", must,
origdoc, only_valid_cert) origdoc, only_valid_cert)
def correctly_signed_response(self, decoded_xml, must=False, origdoc=None): def correctly_signed_response(self, decoded_xml, must=False, origdoc=None, require_response_signature=False):
""" Check if a instance is correctly signed, if we have metadata for """ Check if a instance is correctly signed, if we have metadata for
the IdP that sent the info use that, if not use the key that are in the IdP that sent the info use that, if not use the key that are in
the message if any. the message if any.
@@ -1492,26 +1492,18 @@ class SecurityContext(object):
if response.signature: if response.signature:
self._check_signature(decoded_xml, response, class_name(response), self._check_signature(decoded_xml, response, class_name(response),
origdoc) origdoc)
elif require_response_signature:
raise SignatureError("Signature missing for response")
if isinstance(response, Response) and (response.assertion or if isinstance(response, Response) and (response.assertion or
response.encrypted_assertion): response.encrypted_assertion):
# Try to find the signing cert in the assertion # Try to find the signing cert in the assertion
for assertion in ( for assertion in (
response.assertion or response.encrypted_assertion): response.assertion or response.encrypted_assertion):
if response.encrypted_assertion: if not hasattr(assertion, 'signature') or not assertion.signature:
assertion_list = extension_elements_to_elements(assertion.extension_elements, [saml])
if len(assertion_list) > 0:
assertion = saml.assertion_from_string(str(assertion_list[0]))
response.assertion.append(assertion)
else:
decoded_xml = self.decrypt(assertion.encrypted_data.to_string())
assertion = saml.assertion_from_string(decoded_xml)
response.assertion.append(assertion)
if not assertion.signature:
logger.debug("unsigned") logger.debug("unsigned")
if must: if must:
raise SignatureError("Signature missing") raise SignatureError("Signature missing for assertion")
continue continue
else: else:
logger.debug("signed") logger.debug("signed")