summaryrefslogtreecommitdiff
path: root/src/saml2/encdec.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/saml2/encdec.py')
-rw-r--r--src/saml2/encdec.py277
1 files changed, 277 insertions, 0 deletions
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