diff options
| author | Lorenzo Gil Sanchez <lorenzo.gil.sanchez@gmail.com> | 2012-06-14 07:57:17 +0200 |
|---|---|---|
| committer | Lorenzo Gil Sanchez <lorenzo.gil.sanchez@gmail.com> | 2012-06-14 07:57:17 +0200 |
| commit | d92fa8683a5ae5541df43031f61b9284715206b1 (patch) | |
| tree | 4a88698ab849432a647431991aafd02dff5d4b90 /src/saml2 | |
| parent | 5e6968d0f24a554d1caf62f115e051e2e8faa80a (diff) | |
| parent | 90f2f673a8da76727dcad76049c1ee7a0f325d00 (diff) | |
| download | pysaml2-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.py | 1 | ||||
| -rw-r--r-- | src/saml2/attribute_converter.py | 19 | ||||
| -rw-r--r-- | src/saml2/binding.py | 2 | ||||
| -rw-r--r-- | src/saml2/client.py | 14 | ||||
| -rw-r--r-- | src/saml2/encdec.py | 277 | ||||
| -rw-r--r-- | src/saml2/saml.py | 3 | ||||
| -rw-r--r-- | src/saml2/sigver.py | 4 |
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*"=") |
