Fix, so if no encryption keys exists will the server not encrypt the message.

Extended the test suite with negative tests.
This commit is contained in:
Hans Hörberg 2015-05-19 16:10:35 +02:00
parent eb1f1778db
commit ff7a69fc93
8 changed files with 446 additions and 13 deletions

View File

@ -67,7 +67,8 @@ COMMON_ARGS = [
"cert_handler_extra_class",
"generate_cert_func",
"generate_cert_info",
"verify_encrypt_cert",
"verify_encrypt_cert_advice",
"verify_encrypt_cert_assertion",
"tmp_cert_file",
"tmp_key_file",
"validate_certificate",
@ -97,6 +98,8 @@ AA_IDP_ARGS = [
"sign_assertion",
"sign_response",
"encrypt_assertion",
"encrypted_advice_attributes",
"encrypt_assertion_self_contained",
"want_authn_requests_signed",
"want_authn_requests_only_with_valid_cert",
"provided_attributes",
@ -219,7 +222,8 @@ class Config(object):
self.allow_unsolicited = False
self.extension_schema = {}
self.cert_handler_extra_class = None
self.verify_encrypt_cert = None
self.verify_encrypt_cert_advice = None
self.verify_encrypt_cert_assertion = None
self.generate_cert_func = None
self.generate_cert_info = None
self.tmp_cert_file = None

View File

@ -502,6 +502,14 @@ class Entity(HTTPBase):
else:
msg.extension_elements = extensions
def has_encrypt_cert_in_metadata(self, sp_entity_id):
if sp_entity_id is not None:
_certs = self.metadata.certs(sp_entity_id, "any", "encryption")
if len(_certs) > 0:
return True
return False
def _encrypt_assertion(self, encrypt_cert, sp_entity_id, response, node_xpath=None):
_certs = []
cbxs = CryptoBackendXmlSec1(self.config.xmlsec_binary)
@ -509,7 +517,7 @@ class Entity(HTTPBase):
_certs = []
_certs.append(encrypt_cert)
elif sp_entity_id is not None:
_certs = self.metadata.certs(sp_entity_id, "any", "encrypt")
_certs = self.metadata.certs(sp_entity_id, "any", "encryption")
exception = None
for _cert in _certs:
try:
@ -528,6 +536,7 @@ class Entity(HTTPBase):
pass
if exception:
raise exception
return response
def _response(self, in_response_to, consumer_url=None, status=None,
issuer=None, sign=False, to_sign=None, sp_entity_id=None,
@ -552,7 +561,6 @@ class Entity(HTTPBase):
:return: A Response instance
"""
if not status:
status = success_status_factory()
@ -570,8 +578,13 @@ class Entity(HTTPBase):
if not sign and to_sign and not encrypt_assertion:
return signed_instance_factory(response, self.sec, to_sign)
if encrypt_assertion or (encrypted_advice_attributes and response.assertion.advice is not None and
len(response.assertion.advice.assertion) == 1):
has_encrypt_cert = self.has_encrypt_cert_in_metadata(sp_entity_id)
if not has_encrypt_cert and encrypt_cert_advice is None:
encrypted_advice_attributes = False
if not has_encrypt_cert and encrypt_cert_assertion is None:
encrypt_assertion = False
if encrypt_assertion or (encrypted_advice_attributes and response.assertion.advice is not None and len(response.assertion.advice.assertion) == 1):
if sign:
response.signature = pre_signature_part(response.id,
self.sec.my_cert, 1)

View File

@ -545,7 +545,6 @@ class Server(Entity):
if not verify_encrypt_cert(encrypt_cert_advice):
raise CertificateError("Invalid certificate for encryption!")
if encrypt_assertion:
verify_encrypt_cert = self.config.getattr("verify_encrypt_cert_assertion", "idp")
if verify_encrypt_cert is not None:

View File

@ -0,0 +1,67 @@
from saml2 import BINDING_SOAP
from saml2 import BINDING_HTTP_REDIRECT
from saml2 import BINDING_HTTP_POST
from saml2.saml import NAMEID_FORMAT_PERSISTENT
from saml2.saml import NAME_FORMAT_URI
from pathutils import full_path
from pathutils import xmlsec_path
BASE = "http://localhost:8088"
CONFIG = {
"entityid": "urn:mace:example.com:saml:roland:idp",
"name": "Rolands IdP",
"service": {
"idp": {
"endpoints": {
"single_sign_on_service": [
("%s/sso" % BASE, BINDING_HTTP_REDIRECT)],
"single_logout_service": [
("%s/slo" % BASE, BINDING_SOAP),
("%s/slop" % BASE, BINDING_HTTP_POST)]
},
"policy": {
"default": {
"lifetime": {"minutes": 15},
"attribute_restrictions": None, # means all I have
"name_form": NAME_FORMAT_URI,
},
"urn:mace:example.com:saml:roland:sp": {
"lifetime": {"minutes": 5},
"nameid_format": NAMEID_FORMAT_PERSISTENT,
# "attribute_restrictions":{
# "givenName": None,
# "surName": None,
# }
}
},
"subject_data": full_path("subject_data.db"),
#"domain": "umu.se",
#"name_qualifier": ""
},
},
"debug": 1,
"key_file": full_path("test.key"),
"cert_file": full_path("test.pem"),
"xmlsec_binary": xmlsec_path,
"metadata": [{
"class": "saml2.mdstore.MetaDataFile",
"metadata": [(full_path("metadata_sp_1_no_encryption.xml"), ),
(full_path("vo_metadata.xml"), )],
}],
"attribute_map_dir": full_path("attributemaps"),
"organization": {
"name": "Exempel AB",
"display_name": [("Exempel AB", "se"), ("Example Co.", "en")],
"url": "http://www.example.com/roland",
},
"contact_person": [
{
"given_name": "John",
"sur_name": "Smith",
"email_address": ["john.smith@example.com"],
"contact_type": "technical",
},
],
}

View File

@ -0,0 +1,76 @@
from saml2 import BINDING_SOAP
from saml2 import BINDING_HTTP_REDIRECT
from saml2 import BINDING_HTTP_POST
from saml2.cert import OpenSSLWrapper
from saml2.saml import NAMEID_FORMAT_PERSISTENT
from saml2.saml import NAME_FORMAT_URI
from pathutils import full_path
from pathutils import xmlsec_path
BASE = "http://localhost:8088"
def verify_encrypt_cert(cert_str):
osw = OpenSSLWrapper()
ca_cert_str = osw.read_str_from_file(full_path("root_cert/localhost.ca.crt"))
valid, mess = osw.verify(ca_cert_str, cert_str)
return valid
CONFIG = {
"entityid": "urn:mace:example.com:saml:roland:idp",
"name": "Rolands IdP",
"service": {
"idp": {
"verify_encrypt_cert_advice": verify_encrypt_cert,
"verify_encrypt_cert_assertion": verify_encrypt_cert,
"endpoints": {
"single_sign_on_service": [
("%s/sso" % BASE, BINDING_HTTP_REDIRECT)],
"single_logout_service": [
("%s/slo" % BASE, BINDING_SOAP),
("%s/slop" % BASE, BINDING_HTTP_POST)]
},
"policy": {
"default": {
"lifetime": {"minutes": 15},
"attribute_restrictions": None, # means all I have
"name_form": NAME_FORMAT_URI,
},
"urn:mace:example.com:saml:roland:sp": {
"lifetime": {"minutes": 5},
"nameid_format": NAMEID_FORMAT_PERSISTENT,
# "attribute_restrictions":{
# "givenName": None,
# "surName": None,
# }
}
},
"subject_data": full_path("subject_data.db"),
#"domain": "umu.se",
#"name_qualifier": ""
},
},
"debug": 1,
"key_file": full_path("test.key"),
"cert_file": full_path("test.pem"),
"xmlsec_binary": xmlsec_path,
"metadata": [{
"class": "saml2.mdstore.MetaDataFile",
"metadata": [(full_path("metadata_sp_1.xml"), ),
(full_path("vo_metadata.xml"), )],
}],
"attribute_map_dir": full_path("attributemaps"),
"organization": {
"name": "Exempel AB",
"display_name": [("Exempel AB", "se"), ("Example Co.", "en")],
"url": "http://www.example.com/roland",
},
"contact_person": [
{
"given_name": "John",
"sur_name": "Smith",
"email_address": ["john.smith@example.com"],
"contact_type": "technical",
},
],
}

View File

@ -5,7 +5,30 @@
<ns0:SPSSODescriptor AuthnRequestsSigned="false"
WantAssertionsSigned="true"
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<ns0:KeyDescriptor>
<ns0:KeyDescriptor use="signing">
<ns1:KeyInfo>
<ns1:X509Data>
<ns1:X509Certificate>
MIICsDCCAhmgAwIBAgIJAJrzqSSwmDY9MA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwHhcNMDkxMDA2MTk0OTQxWhcNMDkxMTA1MTk0OTQxWjBF
MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB
gQDJg2cms7MqjniT8Fi/XkNHZNPbNVQyMUMXE9tXOdqwYCA1cc8vQdzkihscQMXy
3iPw2cMggBu6gjMTOSOxECkuvX5ZCclKr8pXAJM5cY6gVOaVO2PdTZcvDBKGbiaN
efiEw5hnoZomqZGp8wHNLAUkwtH9vjqqvxyS/vclc6k2ewIDAQABo4GnMIGkMB0G
A1UdDgQWBBRePsKHKYJsiojE78ZWXccK9K4aJTB1BgNVHSMEbjBsgBRePsKHKYJs
iojE78ZWXccK9K4aJaFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUt
U3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAJrzqSSw
mDY9MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAJSrKOEzHO7TL5cy6
h3qh+3+JAk8HbGBW+cbX6KBCAw/mzU8flK25vnWwXS3dv2FF3Aod0/S7AWNfKib5
U/SA9nJaz/mWeF9S0farz9AQFc8/NSzAzaVq7YbM4F6f6N2FRl7GikdXRCed45j6
mrPzGzk3ECbupFnqyREH3+ZPSdk=
</ns1:X509Certificate>
</ns1:X509Data>
</ns1:KeyInfo>
</ns0:KeyDescriptor>
<ns0:KeyDescriptor use="encryption">
<ns1:KeyInfo>
<ns1:X509Data>
<ns1:X509Certificate>

View File

@ -0,0 +1,74 @@
<?xml version='1.0' encoding='UTF-8'?>
<ns0:EntitiesDescriptor xmlns:ns0="urn:oasis:names:tc:SAML:2.0:metadata"
xmlns:ns1="http://www.w3.org/2000/09/xmldsig#">
<ns0:EntityDescriptor entityID="urn:mace:example.com:saml:roland:sp">
<ns0:SPSSODescriptor AuthnRequestsSigned="false"
WantAssertionsSigned="true"
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<ns0:KeyDescriptor use="signing">
<ns1:KeyInfo>
<ns1:X509Data>
<ns1:X509Certificate>
MIICsDCCAhmgAwIBAgIJAJrzqSSwmDY9MA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwHhcNMDkxMDA2MTk0OTQxWhcNMDkxMTA1MTk0OTQxWjBF
MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB
gQDJg2cms7MqjniT8Fi/XkNHZNPbNVQyMUMXE9tXOdqwYCA1cc8vQdzkihscQMXy
3iPw2cMggBu6gjMTOSOxECkuvX5ZCclKr8pXAJM5cY6gVOaVO2PdTZcvDBKGbiaN
efiEw5hnoZomqZGp8wHNLAUkwtH9vjqqvxyS/vclc6k2ewIDAQABo4GnMIGkMB0G
A1UdDgQWBBRePsKHKYJsiojE78ZWXccK9K4aJTB1BgNVHSMEbjBsgBRePsKHKYJs
iojE78ZWXccK9K4aJaFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUt
U3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAJrzqSSw
mDY9MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAJSrKOEzHO7TL5cy6
h3qh+3+JAk8HbGBW+cbX6KBCAw/mzU8flK25vnWwXS3dv2FF3Aod0/S7AWNfKib5
U/SA9nJaz/mWeF9S0farz9AQFc8/NSzAzaVq7YbM4F6f6N2FRl7GikdXRCed45j6
mrPzGzk3ECbupFnqyREH3+ZPSdk=
</ns1:X509Certificate>
</ns1:X509Data>
</ns1:KeyInfo>
</ns0:KeyDescriptor>
<ns0:AssertionConsumerService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="http://lingon.catalogix.se:8087/" index="1"/>
<ns0:AttributeConsumingService index="1">
<ns0:ServiceName xml:lang="en">
urn:mace:example.com:saml:roland:sp
</ns0:ServiceName>
<ns0:ServiceDescription xml:lang="en">My own SP
</ns0:ServiceDescription>
<ns0:RequestedAttribute FriendlyName="surName"
Name="urn:oid:2.5.4.4"
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
isRequired="true"/>
<ns0:RequestedAttribute FriendlyName="givenName"
Name="urn:oid:2.5.4.42"
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
isRequired="true"/>
<ns0:RequestedAttribute FriendlyName="mail"
Name="urn:oid:0.9.2342.19200300.100.1.3"
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
isRequired="true"/>
<ns0:RequestedAttribute FriendlyName="title"
Name="urn:oid:2.5.4.12"
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
isRequired="false"/>
</ns0:AttributeConsumingService>
</ns0:SPSSODescriptor>
<ns0:Organization>
<ns0:OrganizationName xml:lang="se">AB Exempel
</ns0:OrganizationName>
<ns0:OrganizationDisplayName xml:lang="se">AB Exempel
</ns0:OrganizationDisplayName>
<ns0:OrganizationURL xml:lang="en">http://www.example.org
</ns0:OrganizationURL>
</ns0:Organization>
<ns0:ContactPerson contactType="technical">
<ns0:GivenName>Roland</ns0:GivenName>
<ns0:SurName>Hedberg</ns0:SurName>
<ns0:EmailAddress>tech@eample.com</ns0:EmailAddress>
<ns0:EmailAddress>tech@example.org</ns0:EmailAddress>
<ns0:TelephoneNumber>+46 70 100 0000</ns0:TelephoneNumber>
</ns0:ContactPerson>
</ns0:EntityDescriptor>
</ns0:EntitiesDescriptor>

View File

@ -7,7 +7,7 @@ from urlparse import parse_qs
import uuid
from saml2.cert import OpenSSLWrapper
from saml2.sigver import make_temp
from saml2.sigver import make_temp, EncryptError, CertificateError
from saml2.assertion import Policy
from saml2.authn_context import INTERNETPROTOCOLPASSWORD
from saml2.saml import NameID, NAMEID_FORMAT_TRANSIENT
@ -83,6 +83,7 @@ def generate_cert():
req_cert_str)
return cert_str, req_key_str
class TestServer1():
def setup_class(self):
self.server = Server("idp_conf")
@ -874,11 +875,13 @@ class TestServer1():
self.verify_advice_assertion(resp, decr_text)
def test_encrypted_response_6(self):
_server = Server("idp_conf_verify_cert")
cert_str_advice, cert_key_str_advice = generate_cert()
cert_str_assertion, cert_key_str_assertion = generate_cert()
_resp = self.server.create_authn_response(
_resp = _server.create_authn_response(
self.ava,
"id12", # in_response_to
"http://lingon.catalogix.se:8087/", # consumer_url
@ -899,11 +902,11 @@ class TestServer1():
_, key_file = make_temp("%s" % cert_key_str_assertion, decode=False)
decr_text_1 = self.server.sec.decrypt(_resp, key_file)
decr_text_1 = _server.sec.decrypt(_resp, key_file)
_, key_file = make_temp("%s" % cert_key_str_advice, decode=False)
decr_text_2 = self.server.sec.decrypt(decr_text_1, key_file)
decr_text_2 = _server.sec.decrypt(decr_text_1, key_file)
resp = samlp.response_from_string(decr_text_2)
@ -939,6 +942,180 @@ class TestServer1():
self.verify_advice_assertion(resp, decr_text_2)
def test_encrypted_response_8(self):
try:
_resp = self.server.create_authn_response(
self.ava,
"id12", # in_response_to
"http://lingon.catalogix.se:8087/", # consumer_url
"urn:mace:example.com:saml:roland:sp", # sp_entity_id
name_id=self.name_id,
sign_response=False,
sign_assertion=False,
encrypt_assertion=True,
encrypt_assertion_self_contained=True,
encrypted_advice_attributes=True,
encrypt_cert_advice="whatever",
encrypt_cert_assertion="whatever"
)
assert False, "Must throw an exception"
except EncryptError as ex:
pass
except Exception as ex:
assert False, "Wrong exception!"
try:
_resp = self.server.create_authn_response(
self.ava,
"id12", # in_response_to
"http://lingon.catalogix.se:8087/", # consumer_url
"urn:mace:example.com:saml:roland:sp", # sp_entity_id
name_id=self.name_id,
sign_response=False,
sign_assertion=False,
encrypt_assertion=False,
encrypt_assertion_self_contained=True,
encrypted_advice_attributes=True,
encrypt_cert_advice="whatever",
)
assert False, "Must throw an exception"
except EncryptError as ex:
pass
except Exception as ex:
assert False, "Wrong exception!"
try:
_resp = self.server.create_authn_response(
self.ava,
"id12", # in_response_to
"http://lingon.catalogix.se:8087/", # consumer_url
"urn:mace:example.com:saml:roland:sp", # sp_entity_id
name_id=self.name_id,
sign_response=False,
sign_assertion=False,
encrypt_assertion=True,
encrypt_assertion_self_contained=True,
encrypted_advice_attributes=False,
encrypt_cert_assertion="whatever"
)
assert False, "Must throw an exception"
except EncryptError as ex:
pass
except Exception as ex:
assert False, "Wrong exception!"
_server = Server("idp_conf_verify_cert")
try:
_resp = _server.create_authn_response(
self.ava,
"id12", # in_response_to
"http://lingon.catalogix.se:8087/", # consumer_url
"urn:mace:example.com:saml:roland:sp", # sp_entity_id
name_id=self.name_id,
sign_response=False,
sign_assertion=False,
encrypt_assertion=True,
encrypt_assertion_self_contained=True,
encrypted_advice_attributes=True,
encrypt_cert_advice="whatever",
encrypt_cert_assertion="whatever"
)
assert False, "Must throw an exception"
except CertificateError as ex:
pass
except Exception as ex:
assert False, "Wrong exception!"
try:
_resp = _server.create_authn_response(
self.ava,
"id12", # in_response_to
"http://lingon.catalogix.se:8087/", # consumer_url
"urn:mace:example.com:saml:roland:sp", # sp_entity_id
name_id=self.name_id,
sign_response=False,
sign_assertion=False,
encrypt_assertion=False,
encrypt_assertion_self_contained=True,
encrypted_advice_attributes=True,
encrypt_cert_advice="whatever",
)
assert False, "Must throw an exception"
except CertificateError as ex:
pass
except Exception as ex:
assert False, "Wrong exception!"
try:
_resp = _server.create_authn_response(
self.ava,
"id12", # in_response_to
"http://lingon.catalogix.se:8087/", # consumer_url
"urn:mace:example.com:saml:roland:sp", # sp_entity_id
name_id=self.name_id,
sign_response=False,
sign_assertion=False,
encrypt_assertion=True,
encrypt_assertion_self_contained=True,
encrypted_advice_attributes=False,
encrypt_cert_assertion="whatever"
)
assert False, "Must throw an exception"
except CertificateError as ex:
pass
except Exception as ex:
assert False, "Wrong exception!"
def test_encrypted_response_9(self):
_server = Server("idp_conf_sp_no_encrypt")
_resp = _server.create_authn_response(
self.ava,
"id12", # in_response_to
"http://lingon.catalogix.se:8087/", # consumer_url
"urn:mace:example.com:saml:roland:sp", # sp_entity_id
name_id=self.name_id,
sign_response=False,
sign_assertion=False,
encrypt_assertion=True,
encrypt_assertion_self_contained=True,
encrypted_advice_attributes=True,
)
self.verify_assertion(_resp.assertion.advice.assertion)
_resp = _server.create_authn_response(
self.ava,
"id12", # in_response_to
"http://lingon.catalogix.se:8087/", # consumer_url
"urn:mace:example.com:saml:roland:sp", # sp_entity_id
name_id=self.name_id,
sign_response=False,
sign_assertion=False,
encrypt_assertion=False,
encrypt_assertion_self_contained=True,
encrypted_advice_attributes=True,
)
self.verify_assertion(_resp.assertion.advice.assertion)
_resp = _server.create_authn_response(
self.ava,
"id12", # in_response_to
"http://lingon.catalogix.se:8087/", # consumer_url
"urn:mace:example.com:saml:roland:sp", # sp_entity_id
name_id=self.name_id,
sign_response=False,
sign_assertion=False,
encrypt_assertion=True,
encrypt_assertion_self_contained=True,
encrypted_advice_attributes=False,
)
self.verify_assertion([_resp.assertion])
def test_slo_http_post(self):
soon = time_util.in_a_while(days=1)
sinfo = {
@ -1076,4 +1253,4 @@ class TestServerLogout():
if __name__ == "__main__":
ts = TestServer1()
ts.setup_class()
ts.test_encrypted_response_1()
ts.test_encrypted_response_9()