Merge remote-tracking branch 'upstream/master'
This commit is contained in:
@@ -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
37
example/all.sh
Executable 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
|
@@ -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 ..
|
|
||||||
|
|
@@ -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,
|
||||||
}
|
}
|
2
setup.py
2
setup.py
@@ -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',
|
||||||
|
@@ -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
|
@@ -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
|
||||||
|
@@ -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',
|
||||||
|
@@ -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."
|
||||||
|
@@ -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:
|
||||||
|
@@ -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
|
||||||
|
@@ -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)
|
||||||
|
|
||||||
|
@@ -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)
|
||||||
|
|
||||||
|
@@ -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
|
||||||
|
@@ -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
|
||||||
|
|
||||||
|
@@ -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,
|
||||||
|
@@ -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
|
||||||
|
@@ -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>
|
|
||||||
|
@@ -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
|
||||||
|
Reference in New Issue
Block a user