summaryrefslogtreecommitdiff
path: root/src/saml2
diff options
context:
space:
mode:
authorLorenzo Gil Sanchez <lorenzo.gil.sanchez@gmail.com>2012-06-14 07:57:17 +0200
committerLorenzo Gil Sanchez <lorenzo.gil.sanchez@gmail.com>2012-06-14 07:57:17 +0200
commitd92fa8683a5ae5541df43031f61b9284715206b1 (patch)
tree4a88698ab849432a647431991aafd02dff5d4b90 /src/saml2
parent5e6968d0f24a554d1caf62f115e051e2e8faa80a (diff)
parent90f2f673a8da76727dcad76049c1ee7a0f325d00 (diff)
downloadpysaml2-clean-client-api.tar.gz
Merge branch 'master' into clean-client-apiclean-client-api
Diffstat (limited to 'src/saml2')
-rw-r--r--src/saml2/assertion.py1
-rw-r--r--src/saml2/attribute_converter.py19
-rw-r--r--src/saml2/binding.py2
-rw-r--r--src/saml2/client.py14
-rw-r--r--src/saml2/encdec.py277
-rw-r--r--src/saml2/saml.py3
-rw-r--r--src/saml2/sigver.py4
7 files changed, 289 insertions, 31 deletions
diff --git a/src/saml2/assertion.py b/src/saml2/assertion.py
index 41fbc9ca..b51ec54c 100644
--- a/src/saml2/assertion.py
+++ b/src/saml2/assertion.py
@@ -455,6 +455,7 @@ class Assertion(dict):
:param issuer: Who is issuing the statement
:param authn_class: The authentication class
:param authn_auth: The authentication instance
+ :param authn_decl:
:param encrypt: Whether to encrypt parts or all of the Assertion
:param sec_context: The security context used when encrypting
:return: An Assertion instance
diff --git a/src/saml2/attribute_converter.py b/src/saml2/attribute_converter.py
index 000e5970..e1289e50 100644
--- a/src/saml2/attribute_converter.py
+++ b/src/saml2/attribute_converter.py
@@ -88,25 +88,6 @@ def ac_factory(path=""):
def ac_factory_II(path):
return ac_factory(path)
-#def ac_factory_old(path):
-# acs = []
-#
-# for dir_name, directories, files in os.walk(path):
-# for d in list(directories):
-# if d.startswith('.'):
-# directories.remove(d)
-#
-# if files:
-# atco = AttributeConverter(os.path.basename(dir_name))
-# for name in files:
-# fname = os.path.join(dir_name, name)
-# if name.endswith(".py"):
-# name = name[:-3]
-# atco.set(name, fname)
-# atco.adjust()
-# acs.append(atco)
-# return acs
-
def ava_fro(acs, statement):
""" Translates attributes according to their name_formats into the local
names.
diff --git a/src/saml2/binding.py b/src/saml2/binding.py
index e67b64d9..acef290d 100644
--- a/src/saml2/binding.py
+++ b/src/saml2/binding.py
@@ -64,7 +64,7 @@ def http_post_message(message, location, relay_state="", typ="SAMLRequest"):
response.append("""<script type="text/javascript">""")
response.append(" window.onload = function ()")
- response.append(" { document.forms[0].submit(); ")
+ response.append(" { document.forms[0].submit(); }")
response.append("""</script>""")
response.append("</body>")
diff --git a/src/saml2/client.py b/src/saml2/client.py
index 66113908..045befb0 100644
--- a/src/saml2/client.py
+++ b/src/saml2/client.py
@@ -374,7 +374,7 @@ class Saml2Client(object):
:return: AuthnRequest response
"""
- location = self._sso_location(entityid)
+ location = self._sso_location(entityid, binding)
session_id = sid()
_req_str = "%s" % self._authn_request(session_id, location, vorg=vorg,
@@ -1017,9 +1017,9 @@ class Saml2Client(object):
return None
- def request_to_discovery_service(self, disc_url, return_url="",
- policy="", returnIDParam="",
- is_passive=False ):
+ def discovery_service_request_url(self, disc_url, return_url="",
+ policy="", returnIDParam="",
+ is_passive=False ):
"""
Created the HTTP redirect URL needed to send the user to the
discovery service.
@@ -1050,13 +1050,13 @@ class Saml2Client(object):
params = urllib.urlencode(pdir)
return "%s?%s" % (disc_url, params)
- def get_idp_from_discovery_service(self, query="", url="", returnIDParam=""):
+ def discovery_service_response(self, query="", url="", returnIDParam=""):
"""
- Deal with the reponse url from a Discovery Service
+ Deal with the response url from a Discovery Service
:param url: the url the user was redirected back to
:param returnIDParam: This is where the identifier of the IdP is
- place if it was specified in the query otherwise in 'entityID'
+ place if it was specified in the query as not being 'entityID'
:return: The IdP identifier or "" if none was given
"""
diff --git a/src/saml2/encdec.py b/src/saml2/encdec.py
new file mode 100644
index 00000000..239f5449
--- /dev/null
+++ b/src/saml2/encdec.py
@@ -0,0 +1,277 @@
+import os
+import sys
+
+from subprocess import Popen
+from subprocess import PIPE
+
+from tempfile import NamedTemporaryFile
+
+from saml2.sigver import make_temp
+from saml2.sigver import parse_xmlsec_output
+from saml2.sigver import XmlsecError
+from saml2 import saml
+
+__author__ = 'rohe0002'
+
+import xmlenc as enc
+
+#<EncryptedData
+# xmlns="http://www.w3.org/2001/04/xmlenc#"
+# Type="http://www.w3.org/2001/04/xmlenc#Element">
+# <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc"/>
+# <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
+# <EncryptedKey xmlns="http://www.w3.org/2001/04/xmlenc#">
+# <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/>
+# <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
+# <KeyName/>
+# </KeyInfo>
+# <CipherData>
+# <CipherValue/>
+# </CipherData>
+# </EncryptedKey>
+# </KeyInfo>
+# <CipherData>
+# <CipherValue/>
+# </CipherData>
+#</EncryptedData>
+
+class DecryptionError(Exception):
+ pass
+
+ID_ATTR = "ID"
+#NODE_NAME = "urn:oasis:names:tc:SAML:2.0:assertion:Assertion"
+ENC_DATA = "urn:oasis:names:tc:SAML:2.0:assertion:EncryptedData"
+ENC_KEY_CLASS = "EncryptedKey"
+
+RSA_15 = "http://www.w3.org/2001/04/xmlenc#rsa-1_5"
+RSA_OAEP = "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"
+AES128_CBC="http://www.w3.org/2001/04/xmlenc#aes128-cbc"
+TRIPLE_DES = "http://www.w3.org/2001/04/xmlenc#tripledes-cbc"
+
+# registered xmlsec transforms
+TRANSFORMS = ["base64","enveloped-signature","c14n","c14n-with-comments",
+ "c14n11","c14n11-with-comments","exc-c14n",
+ "exc-c14n-with-comments","xpath","xpath2","xpointer","xslt",
+ "aes128-cbc","aes192-cbc","aes256-cbc","kw-aes128","kw-aes192",
+ "kw-aes256","tripledes-cbc","kw-tripledes","dsa-sha1","hmac-md5",
+ "hmac-ripemd160","hmac-sha1","hmac-sha224","hmac-sha256",
+ "hmac-sha384","hmac-sha512","md5","ripemd160","rsa-md5",
+ "rsa-ripemd160","rsa-sha1","rsa-sha224","rsa-sha256","rsa-sha384",
+ "rsa-sha512","rsa-1_5","rsa-oaep-mgf1p","sha1","sha224","sha256",
+ "sha384","sha512"]
+
+ALGORITHM = {
+ "tripledes-cbc": TRIPLE_DES,
+ "aes128-cbc": AES128_CBC,
+ "rsa-1_5": RSA_15,
+ "rsa-oaep-mgf1p": RSA_OAEP
+}
+
+def template(ident=None, session_key="tripledes-cbc"):
+ """
+ If an assertion is to be signed the signature part has to be preset
+ with which algorithms to be used, this function returns such a
+ preset part.
+
+ :param ident: The identifier of the assertion, so you know which assertion
+ was signed
+ :return: A preset signature part
+ """
+
+ cipher_data = enc.CipherData(cipher_value=enc.CipherValue())
+ encryption_method = enc.EncryptionMethod(algorithm=ALGORITHM[session_key])
+ #key_info = ds.KeyInfo(key_name=ds.KeyName())
+ encrypted_data = enc.EncryptedData(
+ type = "http://www.w3.org/2001/04/xmlenc#Element",
+ encryption_method=encryption_method,
+ #key_info=key_info,
+ cipher_data=cipher_data)
+
+ if ident:
+ encrypted_data.id = "%s" % ident
+
+ return encrypted_data
+
+# xmlsec decrypt --privkey-pem userkey.pem doc-encrypted.xml
+
+def decrypt_message(enctext, xmlsec_binary, key_file=None,
+ key_file_type="privkey-pem", cafile=None,
+ epath=None, id_attr="",
+ node_name="", node_id=None, log=None, debug=False):
+ """ Decrypts an encrypted part of a XML document.
+
+ :param enctext: XML document containing an encrypted part
+ :param xmlsec_binary: The xmlsec1 binaries to be used
+ :param key_file: The key used to decrypt the message
+ :param key_file_type: The key file type
+ :param node_name: The SAML class of the root node in the message
+ :param node_id: The identifier of the root node if any
+ :param id_attr: Should normally be one of "id", "Id" or "ID"
+ :param log: A log function to use when logging
+ :param debug: To debug or not
+ :return: The decrypted document if all was OK otherwise will raise an
+ exception.
+ """
+
+ if not id_attr:
+ id_attr = ID_ATTR
+
+ _, fil = make_temp(enctext, decode=False)
+
+ com_list = [xmlsec_binary, "--decrypt",
+ "--%s" % key_file_type, key_file]
+
+ if key_file_type in ["privkey-pem", "privkey-der", "pkcs8-pem",
+ "pkcs8-der"]:
+ if isinstance(cafile, basestring):
+ com_list.append(cafile)
+ else:
+ com_list.extend(cafile)
+
+ if id_attr:
+ com_list.extend(["--id-attr:%s" % id_attr, node_name])
+
+ elif epath:
+ xpath = create_xpath(epath)
+ com_list.extend(['--node-xpath', xpath])
+
+ # if debug:
+# com_list.append("--store-signatures")
+
+ if node_id:
+ com_list.extend(["--node-id", node_id])
+
+ com_list.append(fil)
+
+ if debug:
+ try:
+ print " ".join(com_list)
+ except TypeError:
+ print "key_file_type", key_file_type
+ print "key_file", key_file
+ print "node_name", node_name
+ print "fil", fil
+ raise
+ print "%s: %s" % (key_file, os.access(key_file, os.F_OK))
+ print "%s: %s" % (fil, os.access(fil, os.F_OK))
+
+ pof = Popen(com_list, stderr=PIPE, stdout=PIPE)
+ p_out = pof.stdout.read()
+ try:
+ p_err = pof.stderr.read()
+ if debug:
+ print p_err
+ verified = parse_xmlsec_output(p_err)
+ except XmlsecError, exc:
+ if log:
+ log.error(60*"=")
+ log.error(p_out)
+ log.error(60*"-")
+ log.error("%s" % exc)
+ log.error(60*"=")
+ raise DecryptionError("%s" % (exc,))
+
+ return verified
+
+# Whole document
+#xmlsec1 encrypt --pubkey-pem ServerKeys/pubkey.pem --session-key des-192
+# --xml-data ClientRequest.xml
+# --output ClientEncrypted.xml EncryptionTemplate.xml
+
+# single value
+#/opt/local/bin/xmlsec1 encrypt --pubkey-cert-pem pubkey.pem
+# --session-key des-192 --xml-data pre_saml2_response.xml
+# --node-xpath '/*[local-name()="Response"]/*[local-name()="Assertion"]/*[local-name()="Subject"]/*[local-name()="EncryptedID"]/text()'
+# encryption_template.xml > enc.out
+
+def create_xpath(path):
+ """
+ :param path: list of element names
+ """
+
+ return "/*".join(['[local-name()="%s"]' % e for e in path]) + "/text()"
+
+def encrypt_using_xmlsec(xmlsec, data, template, epath=None, key=None,
+ key_file=None, key_file_type="pubkey-pem",
+ session_key=None, log=None):
+ """encrypting a value using xmlsec.
+
+ :param xmlsec: Path to the xmlsec1 binary
+ :param data: A XML document from which the value should be picked.
+ :param template: The encyption part template
+ :param epath: Which value to encrypt, if not the whole document
+ should be encrypted.
+ :param key: The key to be used for the encrypting, either this or
+ :param key_file: The file where the key can be found
+ :param key_file_type: pubkey-pem, pubkey-der, pubkey-cert-pem,
+ pubkey-cert-der, privkey-der, privkey-pem, ...
+ :param session_key: Key algorithm
+ :param log: log function
+ :return: The signed statement
+ """
+
+ if not key_file and key:
+ _, key_file = make_temp("%s" % key, ".pem")
+
+ ntf = NamedTemporaryFile()
+ xpath = create_xpath(epath)
+
+ com_list = [xmlsec, "encrypt",
+ "--output", ntf.name,
+ "--xml-data", data,
+ '--node-xpath', xpath,
+ key_file_type, key_file
+ ]
+
+ if session_key:
+ com_list.extend(["--session-key", session_key])
+
+ _, fil = make_temp("%s" % template, decode=False)
+ com_list.append(fil)
+
+ pof = Popen(com_list, stderr=PIPE, stdout=PIPE)
+ p_out = pof.stdout.read()
+ p_err = pof.stderr.read()
+
+ # this doesn't work if --store-signatures are used
+ if p_out == "":
+ ntf.seek(0)
+ encrypted_statement = ntf.read()
+ if not encrypted_statement:
+ if log:
+ log.error(p_err)
+ else:
+ print >> sys.stderr, p_err
+ raise Exception("Encryption failed")
+ else:
+ return encrypted_statement
+ else:
+ print >> sys.stderr, p_out
+ print "E", p_err
+ raise Exception("Encryption failed")
+
+def encrypt_id(response, xmlsec, key_file, key_file_type, identifier,
+ session_key, node_id="", log=None):
+ """
+ :param response: The response as a Response class instance
+ :param xmlsec: Where the xmlsec1 binaries reside
+ :param key_file: Which key file to use
+ :param key_file_type: The type of key file
+ :param identifier: The subject identifier
+ :param session_key: The type of key used to encrypt
+ :return: statement with the subject identifier encrypted
+ """
+ if not response.assertion[0].subject.encrypted_id:
+ response.assertion[0].subject.encrypted_id = saml.EncryptedID(
+ identifier)
+
+ statement = encrypt_using_xmlsec(xmlsec, "%s" % response,
+ template=template(ident=node_id,
+ session_key=session_key),
+ epath=["Response","Assertion","Subject","NameID"],
+ key_file=key_file,
+ key_file_type=key_file_type,
+ session_key=session_key,
+ log=log)
+
+ return statement
diff --git a/src/saml2/saml.py b/src/saml2/saml.py
index 35c4e226..43d169fc 100644
--- a/src/saml2/saml.py
+++ b/src/saml2/saml.py
@@ -145,10 +145,9 @@ class AttributeValueBase(SamlBase):
self.set_text(tree.text)
try:
typ = self.extension_attributes[TYPE_EXTENSION]
- _x = _verify_value_type(typ, getattr(self,"text"))
+ _verify_value_type(typ, getattr(self, "text"))
except KeyError:
pass
- #print _x
class BaseIDAbstractType_(SamlBase):
"""The urn:oasis:names:tc:SAML:2.0:assertion:BaseIDAbstractType element """
diff --git a/src/saml2/sigver.py b/src/saml2/sigver.py
index 468c1494..9a5b0e43 100644
--- a/src/saml2/sigver.py
+++ b/src/saml2/sigver.py
@@ -302,7 +302,7 @@ def pem_format(key):
return "\n".join(["-----BEGIN CERTIFICATE-----",
key,"-----END CERTIFICATE-----"])
-def _parse_xmlsec_output(output):
+def parse_xmlsec_output(output):
""" Parse the output from xmlsec to try to find out if the
command was successfull or not.
@@ -369,7 +369,7 @@ def verify_signature(enctext, xmlsec_binary, cert_file=None, cert_type="pem",
p_err = pof.stderr.read()
if __DEBUG:
print p_err
- verified = _parse_xmlsec_output(p_err)
+ verified = parse_xmlsec_output(p_err)
except XmlsecError, exc:
if log:
log.error(60*"=")