Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Hans Hörberg
2014-04-01 14:01:08 +02:00
19 changed files with 382 additions and 271 deletions

View File

@@ -21,6 +21,10 @@ To make it easy, for me :-), both the IdP and the SP uses the same keys.
To run the setup do To run the setup do
./run.sh ./all.sh start
and then use your favourit webbrowser to look at "http://localhost:8087/whoami" and then use your favourit webbrowser to look at "http://localhost:8087/whoami"
./all stop
will of course stop your IdP and SP.

37
example/all.sh Executable file
View File

@@ -0,0 +1,37 @@
#!/bin/sh
startme() {
cd sp-wsgi
if [ ! -f conf.py ] ; then
cp conf.py.example conf.py
fi
../../tools/make_metadata.py conf > sp.xml
cd ../idp2
if [ ! -f idp_conf.py ] ; then
cp idp_conf.py.example conf.py
fi
../../tools/make_metadata.py idp_conf > idp.xml
cd ../sp-wsgi
./sp.py conf &
cd ../idp2
./idp.py idp_conf &
cd ..
}
stopme() {
pkill -f "sp.py"
pkill -f "idp.py"
}
case "$1" in
start) startme ;;
stop) stopme ;;
restart) stopme; startme ;;
*) echo "usage: $0 start|stop|restart" >&2
exit 1
;;
esac

View File

@@ -1,16 +0,0 @@
#!/bin/sh
cd sp
../../tools/make_metadata.py sp_conf > sp.xml
cd ../idp2
../../tools/make_metadata.py idp_conf > idp.xml
cd ../sp
./sp.py sp_conf &
cd ../idp2
./idp.py idp_conf &
cd ..

View File

@@ -35,7 +35,5 @@ CONFIG = {
"cert_file": "pki/mycert.pem", "cert_file": "pki/mycert.pem",
"xmlsec_binary": xmlsec_path, "xmlsec_binary": xmlsec_path,
"metadata": {"local": ["../idp2/idp.xml"]}, "metadata": {"local": ["../idp2/idp.xml"]},
#"metadata": {"mdfile": ["./swamid2.md"]},
#"metadata": {"local": ["./swamid-2.0.xml"]},
"name_form": NAME_FORMAT_URI, "name_form": NAME_FORMAT_URI,
} }

View File

@@ -66,7 +66,7 @@ if sys.version_info < (2, 7):
setup( setup(
name='pysaml2', name='pysaml2',
version='2.0.0beta', version='2.0.1beta',
description='Python implementation of SAML Version 2 to be used in a WSGI environment', description='Python implementation of SAML Version 2 to be used in a WSGI environment',
# long_description = read("README"), # long_description = read("README"),
author='Roland Hedberg', author='Roland Hedberg',

View File

@@ -24,7 +24,7 @@ import xmlenc
from saml2 import saml from saml2 import saml
from saml2.time_util import instant, in_a_while from saml2.time_util import instant, in_a_while
from saml2.attribute_converter import from_local from saml2.attribute_converter import from_local, get_local_name
from saml2.s_utils import sid, MissingValue from saml2.s_utils import sid, MissingValue
from saml2.s_utils import factory from saml2.s_utils import factory
from saml2.s_utils import assertion_factory from saml2.s_utils import assertion_factory
@@ -78,7 +78,7 @@ def _match(attr, ava):
return None return None
def filter_on_attributes(ava, required=None, optional=None): def filter_on_attributes(ava, required=None, optional=None, acs=None):
""" Filter """ Filter
:param ava: An attribute value assertion as a dictionary :param ava: An attribute value assertion as a dictionary
@@ -93,27 +93,33 @@ def filter_on_attributes(ava, required=None, optional=None):
if required is None: if required is None:
required = [] required = []
nform = "friendly_name"
for attr in required: for attr in required:
found = False try:
nform = "" _name = attr[nform]
for nform in ["friendly_name", "name"]: except KeyError:
try: if nform == "friendly_name":
_fn = _match(attr[nform], ava) _name = get_local_name(acs, attr["name"],
except KeyError: attr["name_format"])
pass
else: else:
if _fn: continue
try:
values = [av["text"] for av in attr["attribute_value"]]
except KeyError:
values = []
res[_fn] = _filter_values(ava[_fn], values, True)
found = True
break
if not found: _fn = _match(_name, ava)
raise MissingValue("Required attribute missing: '%s'" % ( if not _fn: # In the unlikely case that someone has provided us
attr[nform],)) # with URIs as attribute names
_fn = _match(attr["name"], ava)
if _fn:
try:
values = [av["text"] for av in attr["attribute_value"]]
except KeyError:
values = []
res[_fn] = _filter_values(ava[_fn], values, True)
continue
else:
desc = "Required attribute missing: '%s' (%s)" % (attr["name"],
_name)
raise MissingValue(desc)
if optional is None: if optional is None:
optional = [] optional = []
@@ -311,7 +317,8 @@ class Policy(object):
self.compile(restrictions) self.compile(restrictions)
else: else:
self._restrictions = None self._restrictions = None
self.acs = []
def compile(self, restrictions): def compile(self, restrictions):
""" This is only for IdPs or AAs, and it's about limiting what """ This is only for IdPs or AAs, and it's about limiting what
is returned to the SP. is returned to the SP.
@@ -484,7 +491,8 @@ class Policy(object):
ava = filter_attribute_value_assertions(ava, _rest) ava = filter_attribute_value_assertions(ava, _rest)
if required or optional: if required or optional:
ava = filter_on_attributes(ava, required, optional) logger.debug("required: %s, optional: %s" % (required, optional))
ava = filter_on_attributes(ava, required, optional, self.acs)
return ava return ava
@@ -540,8 +548,10 @@ class Assertion(dict):
def __init__(self, dic=None): def __init__(self, dic=None):
dict.__init__(self, dic) dict.__init__(self, dic)
self.acs = []
def _authn_context_decl(self, decl, authn_auth=None):
@staticmethod
def _authn_context_decl(decl, authn_auth=None):
""" """
Construct the authn context with a authn context declaration Construct the authn context with a authn context declaration
:param decl: The authn context declaration :param decl: The authn context declaration
@@ -726,6 +736,8 @@ class Assertion(dict):
:param metadata: Metadata to use :param metadata: Metadata to use
:return: The resulting AVA after the policy is applied :return: The resulting AVA after the policy is applied
""" """
policy.acs = self.acs
ava = policy.restrict(self, sp_entity_id, metadata) ava = policy.restrict(self, sp_entity_id, metadata)
self.update(ava) self.update(ava)
return ava return ava

View File

@@ -255,6 +255,13 @@ def to_local_name(acs, attr):
return attr.friendly_name return attr.friendly_name
def get_local_name(acs, attr, name_format):
for aconv in acs:
#print ac.format, name_format
if aconv.name_format == name_format:
return aconv._fro[attr]
def d_to_local_name(acs, attr): def d_to_local_name(acs, attr):
""" """
:param acs: List of AttributeConverter instances :param acs: List of AttributeConverter instances

View File

@@ -177,6 +177,7 @@ MAP = {
'edupersonaffiliation': EDUPERSON_OID+'1', 'edupersonaffiliation': EDUPERSON_OID+'1',
'eduPersonPrincipalName': EDUPERSON_OID+'6', 'eduPersonPrincipalName': EDUPERSON_OID+'6',
'edupersonprincipalname': EDUPERSON_OID+'6', 'edupersonprincipalname': EDUPERSON_OID+'6',
'eppn': EDUPERSON_OID+'6',
'localityName': X500ATTR_OID+'7', 'localityName': X500ATTR_OID+'7',
'owner': X500ATTR_OID+'32', 'owner': X500ATTR_OID+'32',
'norEduOrgUnitUniqueNumber': NOREDUPERSON_OID+'2', 'norEduOrgUnitUniqueNumber': NOREDUPERSON_OID+'2',

View File

@@ -9,7 +9,6 @@ from os.path import join
from os import remove from os import remove
from Crypto.Util import asn1 from Crypto.Util import asn1
class WrongInput(Exception): class WrongInput(Exception):
pass pass
@@ -23,55 +22,82 @@ class PayloadError(Exception):
class OpenSSLWrapper(object): class OpenSSLWrapper(object):
def __init__(self): def __init__(self):
pass pass
def create_certificate(self, cert_info, request=False, valid_from=0, valid_to=315360000, sn=1, key_length=1024, def create_certificate(self, cert_info, request=False, valid_from=0,
hash_alg="sha256", write_to_file=False, cert_dir="", cipher_passphrase = None): valid_to=315360000, sn=1, key_length=1024,
hash_alg="sha256", write_to_file=False, cert_dir="",
cipher_passphrase=None):
""" """
Can create certificate requests, to be signed later by another certificate with the method Can create certificate requests, to be signed later by another
certificate with the method
create_cert_signed_certificate. If request is True. create_cert_signed_certificate. If request is True.
Can also create self signed root certificates if request is False. This is default behaviour. Can also create self signed root certificates if request is False.
This is default behaviour.
:param cert_info: Contains information about the certificate. :param cert_info: Contains information about the certificate.
Is a dictionary that must contain the keys: Is a dictionary that must contain the keys:
cn = Common name. This part must match the host being authenticated cn = Common name. This part
country_code = Two letter description of the country. must match the host being authenticated
country_code = Two letter description
of the country.
state = State state = State
city = City city = City
organization = Organization, can be a company name. organization = Organization, can be a
organization_unit = A unit at the organization, can be a department. company name.
organization_unit = A unit at the
organization, can be a department.
Example: Example:
cert_info_ca = { cert_info_ca = {
"cn": "company.com", "cn": "company.com",
"country_code": "se", "country_code": "se",
"state": "AC", "state": "AC",
"city": "Dorotea", "city": "Dorotea",
"organization": "Company", "organization":
"organization_unit": "Sales" "Company",
"organization_unit":
"Sales"
} }
:param request: True if this is a request for certificate, that should be signed. :param request: True if this is a request for certificate,
False if this is a self signed certificate, root certificate. that should be signed.
:param valid_from: When the certificate starts to be valid. Amount of seconds from when the False if this is a self signed certificate,
root certificate.
:param valid_from: When the certificate starts to be valid.
Amount of seconds from when the
certificate is generated. certificate is generated.
:param valid_to: How long the certificate will be valid from when it is generated. :param valid_to: How long the certificate will be valid from
The value is in seconds. Default is 315360000 seconds, a.k.a 10 years. when it is generated.
:param sn: Serial number for the certificate. Default is 1. The value is in seconds. Default is
:param key_length: Length of the key to be generated. Defaults to 1024. 315360000 seconds, a.k.a 10 years.
:param hash_alg: Hash algorithm to use for the key. Default is sha256. :param sn: Serial number for the certificate. Default
:param write_to_file: True if you want to write the certificate to a file. The method will then return is 1.
a tuple with path to certificate file and path to key file. :param key_length: Length of the key to be generated. Defaults
False if you want to get the result as strings. The method will then return a tuple to 1024.
with the certificate string and the key as string. :param hash_alg: Hash algorithm to use for the key. Default
WILL OVERWRITE ALL EXISTING FILES WITHOUT ASKING! is sha256.
:param cert_dir: Where to save the files if write_to_file is true. :param write_to_file: True if you want to write the certificate
:param cipher_passphrase A dictionary with cipher and passphrase. Example: to a file. The method will then return
{"cipher": "blowfish", "passphrase": "qwerty"} a tuple with path to certificate file and
:return: string representation of certificate, string representation of private key path to key file.
False if you want to get the result as
strings. The method will then return a tuple
with the certificate string and the key as
string.
WILL OVERWRITE ALL EXISTING FILES WITHOUT
ASKING!
:param cert_dir: Where to save the files if write_to_file is
true.
:param cipher_passphrase A dictionary with cipher and passphrase.
Example::
{"cipher": "blowfish", "passphrase": "qwerty"}
:return: string representation of certificate,
string representation of private key
if write_to_file parameter is False otherwise if write_to_file parameter is False otherwise
path to certificate file, path to private key file path to certificate file, path to private
key file
""" """
cn = cert_info["cn"] cn = cert_info["cn"]
@@ -97,7 +123,7 @@ class OpenSSLWrapper(object):
k = crypto.PKey() k = crypto.PKey()
k.generate_key(crypto.TYPE_RSA, key_length) k.generate_key(crypto.TYPE_RSA, key_length)
# create a self-signed cert # create a self-signed cert
cert = crypto.X509() cert = crypto.X509()
if request: if request:
@@ -113,8 +139,8 @@ class OpenSSLWrapper(object):
cert.get_subject().CN = cn cert.get_subject().CN = cn
if not request: if not request:
cert.set_serial_number(sn) cert.set_serial_number(sn)
cert.gmtime_adj_notBefore(valid_from) #Valid before present time cert.gmtime_adj_notBefore(valid_from) #Valid before present time
cert.gmtime_adj_notAfter(valid_to) #3 650 days cert.gmtime_adj_notAfter(valid_to) #3 650 days
cert.set_issuer(cert.get_subject()) cert.set_issuer(cert.get_subject())
cert.set_pubkey(k) cert.set_pubkey(k)
cert.sign(k, hash_alg) cert.sign(k, hash_alg)
@@ -122,13 +148,16 @@ class OpenSSLWrapper(object):
filesCreated = False filesCreated = False
try: try:
if request: if request:
tmp_cert = crypto.dump_certificate_request(crypto.FILETYPE_PEM, cert) tmp_cert = crypto.dump_certificate_request(crypto.FILETYPE_PEM,
cert)
else: else:
tmp_cert = crypto.dump_certificate(crypto.FILETYPE_PEM, cert) tmp_cert = crypto.dump_certificate(crypto.FILETYPE_PEM, cert)
tmp_key = None tmp_key = None
if cipher_passphrase is not None: if cipher_passphrase is not None:
tmp_key = crypto.dump_privatekey(crypto.FILETYPE_PEM, k, cipher_passphrase["cipher"], tmp_key = crypto.dump_privatekey(crypto.FILETYPE_PEM, k,
cipher_passphrase["passphrase"]) cipher_passphrase["cipher"],
cipher_passphrase[
"passphrase"])
else: else:
tmp_key = crypto.dump_privatekey(crypto.FILETYPE_PEM, k) tmp_key = crypto.dump_privatekey(crypto.FILETYPE_PEM, k)
if write_to_file: if write_to_file:
@@ -172,36 +201,52 @@ class OpenSSLWrapper(object):
return base64.b64encode(str(str_data)) return base64.b64encode(str(str_data))
def create_cert_signed_certificate(self, sign_cert_str, sign_key_str, request_cert_str, hash_alg="sha256", def create_cert_signed_certificate(self, sign_cert_str, sign_key_str,
valid_from=0, valid_to=315360000, sn=1, passphrase=None): request_cert_str, hash_alg="sha256",
valid_from=0, valid_to=315360000, sn=1,
passphrase=None):
""" """
Will sign a certificate request with a give certificate. Will sign a certificate request with a give certificate.
:param sign_cert_str: This certificate will be used to sign with. Must be a string representation of :param sign_cert_str: This certificate will be used to sign with.
the certificate. If you only have a file use the method read_str_from_file to Must be a string representation of
the certificate. If you only have a file
use the method read_str_from_file to
get a string representation. get a string representation.
:param sign_key_str: This is the key for the ca_cert_str represented as a string. :param sign_key_str: This is the key for the ca_cert_str
If you only have a file use the method read_str_from_file to get a string represented as a string.
If you only have a file use the method
read_str_from_file to get a string
representation. representation.
:param request_cert_str: This is the prepared certificate to be signed. Must be a string representation of :param request_cert_str: This is the prepared certificate to be
the requested certificate. If you only have a file use the method read_str_from_file signed. Must be a string representation of
the requested certificate. If you only have
a file use the method read_str_from_file
to get a string representation. to get a string representation.
:param hash_alg: Hash algorithm to use for the key. Default is sha256. :param hash_alg: Hash algorithm to use for the key. Default
:param valid_from: When the certificate starts to be valid. Amount of seconds from when the is sha256.
:param valid_from: When the certificate starts to be valid.
Amount of seconds from when the
certificate is generated. certificate is generated.
:param valid_to: How long the certificate will be valid from when it is generated. :param valid_to: How long the certificate will be valid from
The value is in seconds. Default is 315360000 seconds, a.k.a 10 years. when it is generated.
:param sn: Serial number for the certificate. Default is 1. The value is in seconds. Default is
315360000 seconds, a.k.a 10 years.
:param sn: Serial number for the certificate. Default
is 1.
:param passphrase: Password for the private key in sign_key_str. :param passphrase: Password for the private key in sign_key_str.
:return: String representation of the signed certificate. :return: String representation of the signed
certificate.
""" """
ca_cert = crypto.load_certificate(crypto.FILETYPE_PEM, sign_cert_str) ca_cert = crypto.load_certificate(crypto.FILETYPE_PEM, sign_cert_str)
ca_key = None ca_key = None
if passphrase is not None: if passphrase is not None:
ca_key = crypto.load_privatekey(crypto.FILETYPE_PEM, sign_key_str, passphrase) ca_key = crypto.load_privatekey(crypto.FILETYPE_PEM, sign_key_str,
passphrase)
else: else:
ca_key = crypto.load_privatekey(crypto.FILETYPE_PEM, sign_key_str) ca_key = crypto.load_privatekey(crypto.FILETYPE_PEM, sign_key_str)
req_cert = crypto.load_certificate_request(crypto.FILETYPE_PEM, request_cert_str) req_cert = crypto.load_certificate_request(crypto.FILETYPE_PEM,
request_cert_str)
cert = crypto.X509() cert = crypto.X509()
cert.set_subject(req_cert.get_subject()) cert.set_subject(req_cert.get_subject())
@@ -217,7 +262,8 @@ class OpenSSLWrapper(object):
def verify_chain(self, cert_chain_str_list, cert_str): def verify_chain(self, cert_chain_str_list, cert_str):
""" """
:param cert_chain_str_list: Must be a list of certificate strings, where the first certificate to be validate :param cert_chain_str_list: Must be a list of certificate strings,
where the first certificate to be validate
is in the beginning and the root certificate is last. is in the beginning and the root certificate is last.
:param cert_str: The certificate to be validated. :param cert_str: The certificate to be validated.
:return: :return:
@@ -229,7 +275,8 @@ class OpenSSLWrapper(object):
else: else:
cert_str = tmp_cert_str cert_str = tmp_cert_str
return (True, return (True,
"Signed certificate is valid and correctly signed by CA certificate.") "Signed certificate is valid and correctly signed by CA "
"certificate.")
def certificate_not_valid_yet(self, cert): def certificate_not_valid_yet(self, cert):
starts_to_be_valid = dateutil.parser.parse(cert.get_notBefore()) starts_to_be_valid = dateutil.parser.parse(cert.get_notBefore())
@@ -243,18 +290,24 @@ class OpenSSLWrapper(object):
""" """
Verifies if a certificate is valid and signed by a given certificate. Verifies if a certificate is valid and signed by a given certificate.
:param signing_cert_str: This certificate will be used to verify the signature. Must be a string representation :param signing_cert_str: This certificate will be used to verify the
of the certificate. If you only have a file use the method read_str_from_file to signature. Must be a string representation
of the certificate. If you only have a file
use the method read_str_from_file to
get a string representation. get a string representation.
:param cert_str: This certificate will be verified if it is correct. Must be a string representation :param cert_str: This certificate will be verified if it is
of the certificate. If you only have a file use the method read_str_from_file to correct. Must be a string representation
of the certificate. If you only have a file
use the method read_str_from_file to
get a string representation. get a string representation.
:return: Valid, Message :return: Valid, Message
Valid = True if the certificate is valid, otherwise false. Valid = True if the certificate is valid,
otherwise false.
Message = Why the validation failed. Message = Why the validation failed.
""" """
try: try:
ca_cert = crypto.load_certificate(crypto.FILETYPE_PEM, signing_cert_str) ca_cert = crypto.load_certificate(crypto.FILETYPE_PEM,
signing_cert_str)
cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert_str) cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert_str)
if self.certificate_not_valid_yet(ca_cert): if self.certificate_not_valid_yet(ca_cert):
@@ -270,7 +323,8 @@ class OpenSSLWrapper(object):
return False, "The signed certificate is not valid yet." return False, "The signed certificate is not valid yet."
if ca_cert.get_subject().CN == cert.get_subject().CN: if ca_cert.get_subject().CN == cert.get_subject().CN:
return False, "CN may not be equal for CA certificate and the signed certificate." return False, ("CN may not be equal for CA certificate and the "
"signed certificate.")
cert_algorithm = cert.get_signature_algorithm() cert_algorithm = cert.get_signature_algorithm()
@@ -279,9 +333,9 @@ class OpenSSLWrapper(object):
der_seq = asn1.DerSequence() der_seq = asn1.DerSequence()
der_seq.decode(cert_asn1) der_seq.decode(cert_asn1)
cert_certificate=der_seq[0] cert_certificate = der_seq[0]
#cert_signature_algorithm=der_seq[1] #cert_signature_algorithm=der_seq[1]
cert_signature=der_seq[2] cert_signature = der_seq[2]
cert_signature_decoded = asn1.DerObject() cert_signature_decoded = asn1.DerObject()
cert_signature_decoded.decode(cert_signature) cert_signature_decoded.decode(cert_signature)
@@ -289,12 +343,14 @@ class OpenSSLWrapper(object):
signature_payload = cert_signature_decoded.payload signature_payload = cert_signature_decoded.payload
if signature_payload[0] != '\x00': if signature_payload[0] != '\x00':
return False, "The certificate should not contain any unused bits." return (False,
"The certificate should not contain any unused bits.")
signature = signature_payload[1:] signature = signature_payload[1:]
try: try:
crypto.verify(ca_cert, signature, cert_certificate, cert_algorithm) crypto.verify(ca_cert, signature, cert_certificate,
cert_algorithm)
return True, "Signed certificate is valid and correctly signed by CA certificate." return True, "Signed certificate is valid and correctly signed by CA certificate."
except crypto.Error, e: except crypto.Error, e:
return False, "Certificate is incorrectly signed." return False, "Certificate is incorrectly signed."

View File

@@ -243,7 +243,7 @@ class Base(Entity):
try: try:
args["assertion_consumer_service_url"] = kwargs[ args["assertion_consumer_service_url"] = kwargs[
"assertion_consumer_service_url"] "assertion_consumer_service_url"]
del kwargs["assertion_consumer_service_urls"] del kwargs["assertion_consumer_service_url"]
except KeyError: except KeyError:
try: try:
args["attribute_consuming_service_index"] = str(kwargs[ args["attribute_consuming_service_index"] = str(kwargs[
@@ -301,6 +301,9 @@ class Base(Entity):
except KeyError: except KeyError:
pass pass
if sign is None:
sign = self.authn_requests_signed
if (sign and self.sec.cert_handler.generate_cert()) or \ if (sign and self.sec.cert_handler.generate_cert()) or \
client_crt is not None: client_crt is not None:
with self.lock: with self.lock:

View File

@@ -62,7 +62,8 @@ class DiscoveryServer(Entity):
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
def create_discovery_service_response(self, return_url=None, @staticmethod
def create_discovery_service_response(return_url=None,
returnIDParam="entityID", returnIDParam="entityID",
entity_id=None, **kwargs): entity_id=None, **kwargs):
if return_url is None: if return_url is None:
@@ -87,3 +88,13 @@ class DiscoveryServer(Entity):
return True return True
return False return False
def verify_return(self, entity_id, return_url):
for endp in self.metadata.discovery_response(entity_id):
try:
assert return_url.startswith(endp["location"])
except AssertionError:
pass
else:
return True
return False

View File

@@ -23,6 +23,7 @@ from saml2.saml import NameID
from saml2.saml import Issuer from saml2.saml import Issuer
from saml2.saml import NAMEID_FORMAT_ENTITY from saml2.saml import NAMEID_FORMAT_ENTITY
from saml2.response import LogoutResponse from saml2.response import LogoutResponse
from saml2.response import UnsolicitedResponse
from saml2.time_util import instant from saml2.time_util import instant
from saml2.s_utils import sid from saml2.s_utils import sid
from saml2.s_utils import UnravelError from saml2.s_utils import UnravelError
@@ -839,11 +840,14 @@ class Entity(HTTPBase):
response = response.loads(xmlstr, False, origxml=origxml) 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 raise
except UnsolicitedResponse:
logger.error("Unsolicited response")
raise
except Exception, err: except Exception, err:
if "not well-formed" in "%s" % err: if "not well-formed" in "%s" % err:
logger.error("Not well-formed XML") logger.error("Not well-formed XML")
return None raise
logger.debug("XMLSTR: %s" % xmlstr) logger.debug("XMLSTR: %s" % xmlstr)

View File

@@ -67,11 +67,20 @@ def _since_epoch(cdate):
if len(cdate) < 5: if len(cdate) < 5:
return utc_now() return utc_now()
cdate = cdate[5:] cdate = cdate[5:] # assume short weekday, i.e. do not support obsolete RFC 1036 date format
try: try:
t = time.strptime(cdate, "%d-%b-%Y %H:%M:%S %Z") t = time.strptime(cdate, "%d-%b-%Y %H:%M:%S %Z") # e.g. 18-Apr-2014 12:30:51 GMT
except ValueError: except ValueError:
t = time.strptime(cdate, "%d-%b-%y %H:%M:%S %Z") try:
t = time.strptime(cdate, "%d-%b-%y %H:%M:%S %Z") # e.g. 18-Apr-14 12:30:51 GMT
except ValueError:
try:
t = time.strptime(cdate, "%d %b %Y %H:%M:%S %Z") # e.g. 18 Apr 2014 12:30:51 GMT
except ValueError:
raise Exception, 'ValueError: Date "{0}" does not match any of '.format(cdate) + \
'"%d-%b-%Y %H:%M:%S %Z", ' + \
'"%d-%b-%y %H:%M:%S %Z", ' + \
'"%d %b %Y %H:%M:%S %Z".'
#return int(time.mktime(t)) #return int(time.mktime(t))
return calendar.timegm(t) return calendar.timegm(t)

View File

@@ -456,13 +456,13 @@ def do_spsso_descriptor(conf, cert=None):
ENDPOINTS["sp"]).items(): ENDPOINTS["sp"]).items():
setattr(spsso, endpoint, instlist) setattr(spsso, endpoint, instlist)
# ext = do_endpoints(endps, ENDPOINT_EXT["sp"]) ext = do_endpoints(endps, ENDPOINT_EXT["sp"])
# if ext: if ext:
# if spsso.extensions is None: if spsso.extensions is None:
# spsso.extensions = md.Extensions() spsso.extensions = md.Extensions()
# for vals in ext.values(): for vals in ext.values():
# for val in vals: for val in vals:
# spsso.extensions.add_extension_element(val) spsso.extensions.add_extension_element(val)
if cert: if cert:
encryption_type = conf.encryption_type encryption_type = conf.encryption_type

View File

@@ -204,7 +204,7 @@ def _dummy(_):
def for_me(conditions, myself): def for_me(conditions, myself):
""" Am I among the intended audiences """ """ Am I among the intended audiences """
if not conditions.audience_restriction: # No audience restriction if not conditions.audience_restriction: # No audience restriction
return True return True
for restriction in conditions.audience_restriction: for restriction in conditions.audience_restriction:
@@ -216,19 +216,20 @@ def for_me(conditions, myself):
else: else:
#print "Not for me: %s != %s" % (audience.text.strip(), myself) #print "Not for me: %s != %s" % (audience.text.strip(), myself)
pass pass
return False return False
def authn_response(conf, return_addrs, outstanding_queries=None, timeslack=0, def authn_response(conf, return_addrs, outstanding_queries=None, timeslack=0,
asynchop=True, allow_unsolicited=False, want_assertions_signed=False): asynchop=True, allow_unsolicited=False,
want_assertions_signed=False):
sec = security_context(conf) sec = security_context(conf)
if not timeslack: if not timeslack:
try: try:
timeslack = int(conf.accepted_time_diff) timeslack = int(conf.accepted_time_diff)
except TypeError: except TypeError:
timeslack = 0 timeslack = 0
return AuthnResponse(sec, conf.attribute_converters, conf.entityid, return AuthnResponse(sec, conf.attribute_converters, conf.entityid,
return_addrs, outstanding_queries, timeslack, return_addrs, outstanding_queries, timeslack,
asynchop=asynchop, allow_unsolicited=allow_unsolicited, asynchop=asynchop, allow_unsolicited=allow_unsolicited,
@@ -271,13 +272,13 @@ class StatusResponse(object):
self.require_response_signature = False self.require_response_signature = False
self.not_signed = False self.not_signed = False
self.asynchop = asynchop self.asynchop = asynchop
def _clear(self): def _clear(self):
self.xmlstr = "" self.xmlstr = ""
self.name_id = None self.name_id = None
self.response = None self.response = None
self.not_on_or_after = 0 self.not_on_or_after = 0
def _postamble(self): def _postamble(self):
if not self.response: if not self.response:
logger.error("Response was not correctly signed") logger.error("Response was not correctly signed")
@@ -293,10 +294,10 @@ class StatusResponse(object):
logger.error("Not valid response: %s" % exc.args[0]) logger.error("Not valid response: %s" % exc.args[0])
self._clear() self._clear()
return self return self
self.in_response_to = self.response.in_response_to self.in_response_to = self.response.in_response_to
return self return self
def load_instance(self, instance): def load_instance(self, instance):
if signed(instance): if signed(instance):
# This will check signature on Assertion which is the default # This will check signature on Assertion which is the default
@@ -309,9 +310,9 @@ class StatusResponse(object):
else: else:
self.not_signed = True self.not_signed = True
self.response = instance self.response = instance
return self._postamble() return self._postamble()
def _loads(self, xmldata, decode=True, origxml=None): def _loads(self, xmldata, decode=True, origxml=None):
# own copy # own copy
@@ -319,7 +320,8 @@ 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) require_response_signature=self.require_response_signature)
except TypeError: except TypeError:
@@ -329,11 +331,11 @@ class StatusResponse(object):
except Exception, excp: except Exception, excp:
#logger.exception("EXCEPTION: %s", excp) #logger.exception("EXCEPTION: %s", excp)
raise raise
#print "<", self.response #print "<", self.response
return self._postamble() return self._postamble()
def status_ok(self): def status_ok(self):
if self.response.status: if self.response.status:
status = self.response.status status = self.response.status
@@ -369,7 +371,7 @@ class StatusResponse(object):
def _verify(self): def _verify(self):
if self.request_id and self.in_response_to and \ if self.request_id and self.in_response_to and \
self.in_response_to != self.request_id: self.in_response_to != self.request_id:
logger.error("Not the id I expected: %s != %s" % ( logger.error("Not the id I expected: %s != %s" % (
self.in_response_to, self.request_id)) self.in_response_to, self.request_id))
return None return None
@@ -385,11 +387,11 @@ class StatusResponse(object):
if self.asynchop: if self.asynchop:
if self.response.destination and \ if self.response.destination and \
self.response.destination not in self.return_addrs: self.response.destination not in self.return_addrs:
logger.error("%s not in %s" % (self.response.destination, logger.error("%s not in %s" % (self.response.destination,
self.return_addrs)) self.return_addrs))
return None return None
assert self.issue_instant_ok() assert self.issue_instant_ok()
assert self.status_ok() assert self.status_ok()
return self return self
@@ -408,10 +410,10 @@ class StatusResponse(object):
self.xmlstr = mold.xmlstr self.xmlstr = mold.xmlstr
self.in_response_to = mold.in_response_to self.in_response_to = mold.in_response_to
self.response = mold.response self.response = mold.response
def issuer(self): def issuer(self):
return self.response.issuer.text.strip() return self.response.issuer.text.strip()
class LogoutResponse(StatusResponse): class LogoutResponse(StatusResponse):
msgtype = "logout_response" msgtype = "logout_response"
@@ -430,7 +432,8 @@ class NameIDMappingResponse(StatusResponse):
request_id=0, asynchop=True): request_id=0, asynchop=True):
StatusResponse.__init__(self, sec_context, return_addrs, timeslack, StatusResponse.__init__(self, sec_context, return_addrs, timeslack,
request_id, asynchop) request_id, asynchop)
self.signature_check = self.sec.correctly_signed_name_id_mapping_response self.signature_check = self.sec\
.correctly_signed_name_id_mapping_response
class ManageNameIDResponse(StatusResponse): class ManageNameIDResponse(StatusResponse):
@@ -455,7 +458,8 @@ 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, want_response_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)
@@ -465,7 +469,7 @@ class AuthnResponse(StatusResponse):
self.outstanding_queries = outstanding_queries self.outstanding_queries = outstanding_queries
else: else:
self.outstanding_queries = {} self.outstanding_queries = {}
self.context = "AuthnReq" self.context = "AuthnReq"
self.came_from = "" self.came_from = ""
self.ava = None self.ava = None
self.assertion = None self.assertion = None
@@ -481,19 +485,40 @@ class AuthnResponse(StatusResponse):
except KeyError: except KeyError:
self.extension_schema = {} self.extension_schema = {}
def check_subject_confirmation_in_response_to(self, irp):
for assertion in self.response.assertion:
for _sc in assertion.subject.subject_confirmation:
try:
assert _sc.subject_confirmation_data.in_response_to == irp
except AssertionError:
return False
return True
def loads(self, xmldata, decode=True, origxml=None): def loads(self, xmldata, decode=True, origxml=None):
self._loads(xmldata, decode, origxml) self._loads(xmldata, decode, origxml)
if self.asynchop: if self.asynchop:
if self.in_response_to in self.outstanding_queries: if self.in_response_to in self.outstanding_queries:
self.came_from = self.outstanding_queries[self.in_response_to] self.came_from = self.outstanding_queries[self.in_response_to]
del self.outstanding_queries[self.in_response_to] del self.outstanding_queries[self.in_response_to]
try:
if not self.check_subject_confirmation_in_response_to(
self.in_response_to):
logger.exception(
"Unsolicited response %s" % self.in_response_to)
raise UnsolicitedResponse(
"Unsolicited response: %s" % self.in_response_to)
except AttributeError:
pass
elif self.allow_unsolicited: elif self.allow_unsolicited:
pass pass
else: else:
logger.exception("Unsolicited response %s" % self.in_response_to) logger.exception(
raise UnsolicitedResponse("Unsolicited response: %s" % self.in_response_to) "Unsolicited response %s" % self.in_response_to)
raise UnsolicitedResponse(
"Unsolicited response: %s" % self.in_response_to)
return self return self
def clear(self): def clear(self):
@@ -501,7 +526,7 @@ class AuthnResponse(StatusResponse):
self.came_from = "" self.came_from = ""
self.ava = None self.ava = None
self.assertion = None self.assertion = None
def authn_statement_ok(self, optional=False): def authn_statement_ok(self, optional=False):
try: try:
# the assertion MUST contain one AuthNStatement # the assertion MUST contain one AuthNStatement
@@ -511,7 +536,7 @@ class AuthnResponse(StatusResponse):
return True return True
else: else:
raise raise
authn_statement = self.assertion.authn_statement[0] authn_statement = self.assertion.authn_statement[0]
if authn_statement.session_not_on_or_after: if authn_statement.session_not_on_or_after:
if validate_on_or_after(authn_statement.session_not_on_or_after, if validate_on_or_after(authn_statement.session_not_on_or_after,
@@ -523,7 +548,7 @@ class AuthnResponse(StatusResponse):
return False return False
return True return True
# check authn_statement.session_index # check authn_statement.session_index
def condition_ok(self, lax=False): def condition_ok(self, lax=False):
if self.test: if self.test:
lax = True lax = True
@@ -541,7 +566,8 @@ class AuthnResponse(StatusResponse):
# if both are present NotBefore must be earlier than NotOnOrAfter # if both are present NotBefore must be earlier than NotOnOrAfter
if conditions.not_before and conditions.not_on_or_after: if conditions.not_before and conditions.not_on_or_after:
if not later_than(conditions.not_on_or_after, conditions.not_before): if not later_than(conditions.not_on_or_after,
conditions.not_before):
return False return False
try: try:
@@ -562,10 +588,11 @@ class AuthnResponse(StatusResponse):
if not lax: if not lax:
raise Exception("Not for me!!!") raise Exception("Not for me!!!")
if conditions.condition: # extra conditions if conditions.condition: # extra conditions
for cond in conditions.condition: for cond in conditions.condition:
try: try:
if cond.extension_attributes[XSI_TYPE] in self.extension_schema: if cond.extension_attributes[
XSI_TYPE] in self.extension_schema:
pass pass
else: else:
raise Exception("Unknown condition") raise Exception("Unknown condition")
@@ -582,9 +609,9 @@ class AuthnResponse(StatusResponse):
:param attribute_statement: A SAML.AttributeStatement which might :param attribute_statement: A SAML.AttributeStatement which might
contain both encrypted attributes and attributes. contain both encrypted attributes and attributes.
""" """
# _node_name = [ # _node_name = [
# "urn:oasis:names:tc:SAML:2.0:assertion:EncryptedData", # "urn:oasis:names:tc:SAML:2.0:assertion:EncryptedData",
# "urn:oasis:names:tc:SAML:2.0:assertion:EncryptedAttribute"] # "urn:oasis:names:tc:SAML:2.0:assertion:EncryptedAttribute"]
for encattr in attribute_statement.encrypted_attribute: for encattr in attribute_statement.encrypted_attribute:
if not encattr.encrypted_key: if not encattr.encrypted_key:
@@ -624,7 +651,7 @@ class AuthnResponse(StatusResponse):
if data.address: if data.address:
if not valid_address(data.address): if not valid_address(data.address):
return False return False
# verify that I got it from the correct sender # verify that I got it from the correct sender
# These two will raise exception if untrue # These two will raise exception if untrue
validate_on_or_after(data.not_on_or_after, self.timeslack) validate_on_or_after(data.not_on_or_after, self.timeslack)
@@ -650,7 +677,8 @@ class AuthnResponse(StatusResponse):
logger.info("outstanding queries: %s" % ( logger.info("outstanding queries: %s" % (
self.outstanding_queries.keys(),)) self.outstanding_queries.keys(),))
raise Exception( raise Exception(
"Combination of session id and requestURI I don't recall") "Combination of session id and requestURI I don't "
"recall")
return True return True
def _holder_of_key_confirmed(self, data): def _holder_of_key_confirmed(self, data):
@@ -687,12 +715,12 @@ class AuthnResponse(StatusResponse):
subject_confirmation.method,)) subject_confirmation.method,))
subjconf.append(subject_confirmation) subjconf.append(subject_confirmation)
if not subjconf: if not subjconf:
raise VerificationError("No valid subject confirmation") raise VerificationError("No valid subject confirmation")
subject.subject_confirmation = subjconf subject.subject_confirmation = subjconf
# The subject must contain a name_id # The subject must contain a name_id
try: try:
assert subject.name_id assert subject.name_id
@@ -709,19 +737,19 @@ class AuthnResponse(StatusResponse):
logger.info("Subject NameID: %s" % self.name_id) logger.info("Subject NameID: %s" % self.name_id)
return self.name_id return self.name_id
def _assertion(self, assertion): def _assertion(self, assertion):
self.assertion = assertion self.assertion = assertion
logger.debug("assertion context: %s" % (self.context,)) logger.debug("assertion context: %s" % (self.context,))
logger.debug("assertion keys: %s" % (assertion.keyswv())) logger.debug("assertion keys: %s" % (assertion.keyswv()))
logger.debug("outstanding_queries: %s" % (self.outstanding_queries,)) logger.debug("outstanding_queries: %s" % (self.outstanding_queries,))
#if self.context == "AuthnReq" or self.context == "AttrQuery": #if self.context == "AuthnReq" or self.context == "AttrQuery":
if self.context == "AuthnReq": if self.context == "AuthnReq":
self.authn_statement_ok() self.authn_statement_ok()
# elif self.context == "AttrQuery": # elif self.context == "AttrQuery":
# self.authn_statement_ok(True) # self.authn_statement_ok(True)
if not self.condition_ok(): if not self.condition_ok():
raise VerificationError("Condition not OK") raise VerificationError("Condition not OK")
@@ -732,7 +760,7 @@ class AuthnResponse(StatusResponse):
self.ava = self.get_identity() self.ava = self.get_identity()
logger.debug("--- AVA: %s" % (self.ava,)) logger.debug("--- AVA: %s" % (self.ava,))
try: try:
self.get_subject() self.get_subject()
if self.asynchop: if self.asynchop:
@@ -744,7 +772,7 @@ class AuthnResponse(StatusResponse):
except Exception: except Exception:
logger.exception("get subject") logger.exception("get subject")
raise raise
def _encrypted_assertion(self, xmlstr): def _encrypted_assertion(self, xmlstr):
if xmlstr.encrypted_data: if xmlstr.encrypted_data:
assertion_str = self.sec.decrypt(xmlstr.encrypted_data.to_string()) assertion_str = self.sec.decrypt(xmlstr.encrypted_data.to_string())
@@ -765,7 +793,7 @@ class AuthnResponse(StatusResponse):
logger.debug("Decrypted Assertion: %s" % assertion) logger.debug("Decrypted Assertion: %s" % assertion)
return self._assertion(assertion) return self._assertion(assertion)
def parse_assertion(self): def parse_assertion(self):
if self.context == "AuthnQuery": if self.context == "AuthnQuery":
# can contain one or more assertions # can contain one or more assertions
@@ -773,10 +801,10 @@ class AuthnResponse(StatusResponse):
else: # This is a saml2int limitation else: # This is a saml2int limitation
try: try:
assert len(self.response.assertion) == 1 or \ assert len(self.response.assertion) == 1 or \
len(self.response.encrypted_assertion) == 1 len(self.response.encrypted_assertion) == 1
except AssertionError: except AssertionError:
raise Exception("No assertion part") raise Exception("No assertion part")
if self.response.assertion: if self.response.assertion:
logger.debug("***Unencrypted response***") logger.debug("***Unencrypted response***")
for assertion in self.response.assertion: for assertion in self.response.assertion:
@@ -793,7 +821,7 @@ class AuthnResponse(StatusResponse):
def verify(self): def verify(self):
""" Verify that the assertion is syntactically correct and """ Verify that the assertion is syntactically correct and
the signature is correct if present.""" the signature is correct if present."""
try: try:
self._verify() self._verify()
except AssertionError: except AssertionError:
@@ -807,15 +835,15 @@ class AuthnResponse(StatusResponse):
else: else:
logger.error("Could not parse the assertion") logger.error("Could not parse the assertion")
return None return None
def session_id(self): def session_id(self):
""" Returns the SessionID of the response """ """ Returns the SessionID of the response """
return self.response.in_response_to return self.response.in_response_to
def id(self): def id(self):
""" Return the ID of the response """ """ Return the ID of the response """
return self.response.id return self.response.id
def authn_info(self): def authn_info(self):
res = [] res = []
for astat in self.assertion.authn_statement: for astat in self.assertion.authn_statement:
@@ -858,7 +886,7 @@ class AuthnResponse(StatusResponse):
return {"ava": self.ava, "name_id": self.name_id, return {"ava": self.ava, "name_id": self.name_id,
"came_from": self.came_from, "issuer": self.issuer(), "came_from": self.came_from, "issuer": self.issuer(),
"not_on_or_after": nooa, "authn_info": self.authn_info()} "not_on_or_after": nooa, "authn_info": self.authn_info()}
def __str__(self): def __str__(self):
return "%s" % self.xmlstr return "%s" % self.xmlstr
@@ -873,7 +901,9 @@ class AuthnResponse(StatusResponse):
correct = 0 correct = 0
for subject_conf in self.assertion.subject.subject_confirmation: for subject_conf in self.assertion.subject.subject_confirmation:
if subject_conf.subject_confirmation_data.address: if subject_conf.subject_confirmation_data is None:
correct += 1 # In reality undefined
elif subject_conf.subject_confirmation_data.address:
if subject_conf.subject_confirmation_data.address == address: if subject_conf.subject_confirmation_data.address == address:
correct += 1 correct += 1
else: else:
@@ -890,7 +920,6 @@ class AuthnQueryResponse(AuthnResponse):
def __init__(self, sec_context, attribute_converters, entity_id, def __init__(self, sec_context, attribute_converters, entity_id,
return_addrs=None, timeslack=0, asynchop=False, test=False): return_addrs=None, timeslack=0, asynchop=False, test=False):
AuthnResponse.__init__(self, sec_context, attribute_converters, AuthnResponse.__init__(self, sec_context, attribute_converters,
entity_id, return_addrs, timeslack=timeslack, entity_id, return_addrs, timeslack=timeslack,
asynchop=asynchop, test=test) asynchop=asynchop, test=test)
@@ -908,7 +937,6 @@ class AttributeResponse(AuthnResponse):
def __init__(self, sec_context, attribute_converters, entity_id, def __init__(self, sec_context, attribute_converters, entity_id,
return_addrs=None, timeslack=0, asynchop=False, test=False): return_addrs=None, timeslack=0, asynchop=False, test=False):
AuthnResponse.__init__(self, sec_context, attribute_converters, AuthnResponse.__init__(self, sec_context, attribute_converters,
entity_id, return_addrs, timeslack=timeslack, entity_id, return_addrs, timeslack=timeslack,
asynchop=asynchop, test=test) asynchop=asynchop, test=test)
@@ -939,7 +967,6 @@ class ArtifactResponse(AuthnResponse):
def __init__(self, sec_context, attribute_converters, entity_id, def __init__(self, sec_context, attribute_converters, entity_id,
return_addrs=None, timeslack=0, asynchop=False, test=False): return_addrs=None, timeslack=0, asynchop=False, test=False):
AuthnResponse.__init__(self, sec_context, attribute_converters, AuthnResponse.__init__(self, sec_context, attribute_converters,
entity_id, return_addrs, timeslack=timeslack, entity_id, return_addrs, timeslack=timeslack,
asynchop=asynchop, test=test) asynchop=asynchop, test=test)
@@ -951,14 +978,15 @@ class ArtifactResponse(AuthnResponse):
def response_factory(xmlstr, conf, return_addrs=None, outstanding_queries=None, def response_factory(xmlstr, conf, return_addrs=None, outstanding_queries=None,
timeslack=0, decode=True, request_id=0, origxml=None, timeslack=0, decode=True, request_id=0, origxml=None,
asynchop=True, allow_unsolicited=False, want_assertions_signed=False): asynchop=True, allow_unsolicited=False,
want_assertions_signed=False):
sec_context = security_context(conf) sec_context = security_context(conf)
if not timeslack: if not timeslack:
try: try:
timeslack = int(conf.accepted_time_diff) timeslack = int(conf.accepted_time_diff)
except TypeError: except TypeError:
timeslack = 0 timeslack = 0
attribute_converters = conf.attribute_converters attribute_converters = conf.attribute_converters
entity_id = conf.entityid entity_id = conf.entityid
extension_schema = conf.extension_schema extension_schema = conf.extension_schema
@@ -983,9 +1011,10 @@ def response_factory(xmlstr, conf, return_addrs=None, outstanding_queries=None,
asynchop=asynchop) asynchop=asynchop)
logoutresp.update(response) logoutresp.update(response)
return logoutresp return logoutresp
return response return response
# =========================================================================== # ===========================================================================
# A class of it's own # A class of it's own

View File

@@ -308,6 +308,7 @@ class Server(Entity):
#if identity: #if identity:
_issuer = self._issuer(issuer) _issuer = self._issuer(issuer)
ast = Assertion(identity) ast = Assertion(identity)
ast.acs = self.config.getattr("attribute_converters", "idp")
if policy is None: if policy is None:
policy = Policy() policy = Policy()
try: try:
@@ -521,7 +522,6 @@ class Server(Entity):
try: try:
_authn = authn _authn = authn
response = None
if (sign_assertion or sign_response) and self.sec.cert_handler.generate_cert(): if (sign_assertion or sign_response) and self.sec.cert_handler.generate_cert():
with self.lock: with self.lock:
self.sec.cert_handler.update_cert(True) self.sec.cert_handler.update_cert(True)
@@ -536,7 +536,8 @@ class Server(Entity):
sign_assertion=sign_assertion, sign_assertion=sign_assertion,
sign_response=sign_response, sign_response=sign_response,
best_effort=best_effort, best_effort=best_effort,
encrypt_assertion=encrypt_assertion, encrypt_cert=encrypt_cert) encrypt_assertion=encrypt_assertion,
encrypt_cert=encrypt_cert)
return self._authn_response(in_response_to, # in_response_to return self._authn_response(in_response_to, # in_response_to
destination, # consumer_url destination, # consumer_url
sp_entity_id, # sp_entity_id sp_entity_id, # sp_entity_id
@@ -548,7 +549,8 @@ class Server(Entity):
sign_assertion=sign_assertion, sign_assertion=sign_assertion,
sign_response=sign_response, sign_response=sign_response,
best_effort=best_effort, best_effort=best_effort,
encrypt_assertion=encrypt_assertion, encrypt_cert=encrypt_cert) encrypt_assertion=encrypt_assertion,
encrypt_cert=encrypt_cert)
except MissingValue, exc: except MissingValue, exc:
return self.create_error_response(in_response_to, destination, return self.create_error_response(in_response_to, destination,

View File

@@ -1011,6 +1011,7 @@ def security_context(conf, debug=None):
tmp_key_file=conf.tmp_key_file, tmp_key_file=conf.tmp_key_file,
validate_certificate=conf.validate_certificate) validate_certificate=conf.validate_certificate)
def encrypt_cert_from_item(item): def encrypt_cert_from_item(item):
_encrypt_cert = None _encrypt_cert = None
try: try:
@@ -1031,6 +1032,7 @@ def encrypt_cert_from_item(item):
return None return None
return _encrypt_cert return _encrypt_cert
class CertHandlerExtra(object): class CertHandlerExtra(object):
def __init__(self): def __init__(self):
pass pass
@@ -1488,7 +1490,8 @@ 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,only_valid_cert=False, def correctly_signed_response(self, decoded_xml, must=False, origdoc=None,
only_valid_cert=False,
require_response_signature=False, **kwargs): require_response_signature=False, **kwargs):
""" 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

View File

@@ -1,82 +1,32 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version='1.0' encoding='UTF-8'?>
<ns0:EntityDescriptor xmlns:ns0="urn:oasis:names:tc:SAML:2.0:metadata" <ns0:EntityDescriptor xmlns:ns0="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ns1="urn:oasis:names:tc:SAML:metadata:attribute" xmlns:ns2="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:ns4="urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol" xmlns:ns5="http://www.w3.org/2000/09/xmldsig#" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" entityID="urn:mace:example.com:saml:roland:sp"><ns0:Extensions><ns1:EntityAttributes><ns2:Attribute Name="http://macedir.org/entity-category"><ns2:AttributeValue xsi:type="xs:string">http://www.swamid.se/category/sfs-1993-1153</ns2:AttributeValue><ns2:AttributeValue xsi:type="xs:string">http://www.swamid.se/category/hei-service</ns2:AttributeValue></ns2:Attribute></ns1:EntityAttributes></ns0:Extensions><ns0:SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"><ns0:Extensions><ns4:DiscoveryResponse Binding="urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol" Location="http://lingon.catalogix.se:8087/disco" index="1" /></ns0:Extensions><ns0:KeyDescriptor use="encryption"><ns5:KeyInfo><ns5:X509Data><ns5:X509Certificate>MIICsDCCAhmgAwIBAgIJAJrzqSSwmDY9MA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
xmlns:ns1="urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol" BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
xmlns:ns2="http://www.w3.org/2000/09/xmldsig#" aWRnaXRzIFB0eSBMdGQwHhcNMDkxMDA2MTk0OTQxWhcNMDkxMTA1MTk0OTQxWjBF
entityID="urn:mace:example.com:saml:roland:sp"> MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50
<ns0:SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="true" ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"> gQDJg2cms7MqjniT8Fi/XkNHZNPbNVQyMUMXE9tXOdqwYCA1cc8vQdzkihscQMXy
<ns0:Extensions> 3iPw2cMggBu6gjMTOSOxECkuvX5ZCclKr8pXAJM5cY6gVOaVO2PdTZcvDBKGbiaN
<ns1:DiscoveryResponse efiEw5hnoZomqZGp8wHNLAUkwtH9vjqqvxyS/vclc6k2ewIDAQABo4GnMIGkMB0G
Binding="urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol" A1UdDgQWBBRePsKHKYJsiojE78ZWXccK9K4aJTB1BgNVHSMEbjBsgBRePsKHKYJs
Location="http://lingon.catalogix.se:8087/disco" index="1"/> iojE78ZWXccK9K4aJaFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUt
</ns0:Extensions> U3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAJrzqSSw
<ns0:KeyDescriptor use="signing"> mDY9MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAJSrKOEzHO7TL5cy6
<ns2:KeyInfo> h3qh+3+JAk8HbGBW+cbX6KBCAw/mzU8flK25vnWwXS3dv2FF3Aod0/S7AWNfKib5
<ns2:X509Data> U/SA9nJaz/mWeF9S0farz9AQFc8/NSzAzaVq7YbM4F6f6N2FRl7GikdXRCed45j6
<ns2:X509Certificate> mrPzGzk3ECbupFnqyREH3+ZPSdk=
MIICsDCCAhmgAwIBAgIJAJrzqSSwmDY9MA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV </ns5:X509Certificate></ns5:X509Data></ns5:KeyInfo></ns0:KeyDescriptor><ns0:KeyDescriptor use="signing"><ns5:KeyInfo><ns5:X509Data><ns5:X509Certificate>MIICsDCCAhmgAwIBAgIJAJrzqSSwmDY9MA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwHhcNMDkxMDA2MTk0OTQxWhcNMDkxMTA1MTk0OTQxWjBF aWRnaXRzIFB0eSBMdGQwHhcNMDkxMDA2MTk0OTQxWhcNMDkxMTA1MTk0OTQxWjBF
MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50 MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB
gQDJg2cms7MqjniT8Fi/XkNHZNPbNVQyMUMXE9tXOdqwYCA1cc8vQdzkihscQMXy gQDJg2cms7MqjniT8Fi/XkNHZNPbNVQyMUMXE9tXOdqwYCA1cc8vQdzkihscQMXy
3iPw2cMggBu6gjMTOSOxECkuvX5ZCclKr8pXAJM5cY6gVOaVO2PdTZcvDBKGbiaN 3iPw2cMggBu6gjMTOSOxECkuvX5ZCclKr8pXAJM5cY6gVOaVO2PdTZcvDBKGbiaN
efiEw5hnoZomqZGp8wHNLAUkwtH9vjqqvxyS/vclc6k2ewIDAQABo4GnMIGkMB0G efiEw5hnoZomqZGp8wHNLAUkwtH9vjqqvxyS/vclc6k2ewIDAQABo4GnMIGkMB0G
A1UdDgQWBBRePsKHKYJsiojE78ZWXccK9K4aJTB1BgNVHSMEbjBsgBRePsKHKYJs A1UdDgQWBBRePsKHKYJsiojE78ZWXccK9K4aJTB1BgNVHSMEbjBsgBRePsKHKYJs
iojE78ZWXccK9K4aJaFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUt iojE78ZWXccK9K4aJaFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUt
U3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAJrzqSSw U3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAJrzqSSw
mDY9MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAJSrKOEzHO7TL5cy6 mDY9MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAJSrKOEzHO7TL5cy6
h3qh+3+JAk8HbGBW+cbX6KBCAw/mzU8flK25vnWwXS3dv2FF3Aod0/S7AWNfKib5 h3qh+3+JAk8HbGBW+cbX6KBCAw/mzU8flK25vnWwXS3dv2FF3Aod0/S7AWNfKib5
U/SA9nJaz/mWeF9S0farz9AQFc8/NSzAzaVq7YbM4F6f6N2FRl7GikdXRCed45j6 U/SA9nJaz/mWeF9S0farz9AQFc8/NSzAzaVq7YbM4F6f6N2FRl7GikdXRCed45j6
mrPzGzk3ECbupFnqyREH3+ZPSdk= mrPzGzk3ECbupFnqyREH3+ZPSdk=
</ns2:X509Certificate> </ns5:X509Certificate></ns5:X509Data></ns5:KeyInfo></ns0:KeyDescriptor><ns0:ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="http://lingon.catalogix.se:8087/ars" index="1" /><ns0:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="http://lingon.catalogix.se:8087/sls" /><ns0:ManageNameIDService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="http://lingon.catalogix.se:8087/mni/soap" /><ns0:ManageNameIDService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://lingon.catalogix.se:8087/mni/post" /><ns0:ManageNameIDService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://lingon.catalogix.se:8087/mni/redirect" /><ns0:ManageNameIDService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" Location="http://lingon.catalogix.se:8087/mni/art" /><ns0:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</ns0:NameIDFormat><ns0:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</ns0:NameIDFormat><ns0:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://lingon.catalogix.se:8087/" index="1" /><ns0:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:PAOS" Location="http://lingon.catalogix.se:8087/paos" index="2" /><ns0:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://lingon.catalogix.se:8087/redirect" index="3" /></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>
</ns2:X509Data>
</ns2:KeyInfo>
</ns0:KeyDescriptor>
<ns0:ArtifactResolutionService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
Location="http://lingon.catalogix.se:8087/ars" index="1"/>
<ns0:SingleLogoutService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
Location="http://lingon.catalogix.se:8087/sls"/>
<ns0:ManageNameIDService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
Location="http://lingon.catalogix.se:8087/mni/soap"/>
<ns0:ManageNameIDService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="http://lingon.catalogix.se:8087/mni/post"/>
<ns0:ManageNameIDService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
Location="http://lingon.catalogix.se:8087/mni/redirect"/>
<ns0:ManageNameIDService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"
Location="http://lingon.catalogix.se:8087/mni/art"/>
<ns0:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient
</ns0:NameIDFormat>
<ns0:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
</ns0:NameIDFormat>
<ns0:AssertionConsumerService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="http://lingon.catalogix.se:8087/" index="1"/>
<ns0:AssertionConsumerService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:PAOS"
Location="http://lingon.catalogix.se:8087/paos" index="2"/>
<ns0:AssertionConsumerService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
Location="http://lingon.catalogix.se:8087/redirect" index="3"/>
</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>

View File

@@ -1,2 +1,3 @@
#!/bin/sh
curl -O -G http://md.swamid.se/md/swamid-2.0.xml curl -O -G http://md.swamid.se/md/swamid-2.0.xml
mdexport.py -t local -o swamid2.md swamid-2.0.xml mdexport.py -t local -o swamid2.md swamid-2.0.xml