summaryrefslogtreecommitdiff
path: root/src/saml2
diff options
context:
space:
mode:
authorRoland Hedberg <roland.hedberg@adm.umu.se>2009-10-30 15:43:11 +0100
committerRoland Hedberg <roland.hedberg@adm.umu.se>2009-10-30 15:43:11 +0100
commitfbcf5084242c849791032bbbe881309111937ab0 (patch)
treefbb73271c8f813cb01b307f910b269129dc6231e /src/saml2
parent3a096a459357b733c151472ef2cbf569f789a482 (diff)
downloadpysaml2-fbcf5084242c849791032bbbe881309111937ab0.tar.gz
Added license info, fixed signature part and added documentation
Diffstat (limited to 'src/saml2')
-rw-r--r--src/saml2/sigver.py202
1 files changed, 138 insertions, 64 deletions
diff --git a/src/saml2/sigver.py b/src/saml2/sigver.py
index 448f58ef..513b3708 100644
--- a/src/saml2/sigver.py
+++ b/src/saml2/sigver.py
@@ -1,6 +1,26 @@
-""" functions connected to signing and verifying """
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2009 UmeƄ University
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+""" Functions connected to signing and verifying.
+Based on the use of xmlsec1 binaries and not the python xmlsec module.
+"""
from saml2 import samlp
+import xmldsig as ds
from tempfile import NamedTemporaryFile
from subprocess import Popen, PIPE
import base64
@@ -14,9 +34,18 @@ ENC_NODE_NAME = "urn:oasis:names:tc:SAML:2.0:assertion:EncryptedAssertion"
_TEST_ = False
def decrypt( input, key_file, xmlsec_binary, log=None):
+ """ Decrypting an encrypted text by the use of a private key.
+
+ :param input: The encrypted text as a string
+ :param key_file: The name of the key file
+ :param xmlsec_binary: Where on the computer the xmlsec binary is.
+ :param log: A reference to a logging instance.
+ :return: The decrypted text
+ """
log and log.info("input len: %d" % len(input))
fil_p, fil = make_temp("%s" % input, decode=False)
ntf = NamedTemporaryFile()
+
log and log.info("xmlsec binary: %s" % xmlsec_binary)
com_list = [xmlsec_binary, "--decrypt",
"--privkey-pem", key_file,
@@ -27,27 +56,33 @@ def decrypt( input, key_file, xmlsec_binary, log=None):
log and log.info("Decrypt command: %s" % " ".join(com_list))
result = Popen(com_list, stderr=PIPE).communicate()
log and log.info("Decrypt result: %s" % (result,))
+
ntf.seek(0)
return ntf.read()
def create_id():
+ """ Create a string of 40 random characters from the set [a-p],
+ can be used as a unique identifier of objects.
+
+ :return: The string of random characters
+ """
ret = ""
for _ in range(40):
ret = ret + chr(random.randint(0, 15) + ord('a'))
return ret
def make_temp(string, suffix="", decode=True):
- """ xmlsec needs files in some cases and I have string hence the
- need for this function, that creates as temporary file with the
+ """ xmlsec needs files in some cases where only strings exist, hence the
+ need for this function. It creates a temporary file with the
string as only content.
:param string: The information to be placed in the file
:param suffix: The temporary file might have to have a specific
suffix in certain circumstances.
:param decode: The input string might be base64 coded. If so it
- must be decoded before placed in the file.
+ must, in some cases, be decoded before placed in the file.
:return: 2-tuple with file pointer ( so the calling function can
- close the file) and filename (which is then needed by the
+ close the file) and filename (which is for instance needed by the
xmlsec function).
"""
ntf = NamedTemporaryFile(suffix=suffix)
@@ -57,40 +92,7 @@ def make_temp(string, suffix="", decode=True):
ntf.write(string)
ntf.seek(0)
return ntf, ntf.name
-
-def cert_from_encrypted_assertion(enc_assertion):
-# <saml2:EncryptedAssertion xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">
-# <xenc:EncryptedData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"
-# Id="_e569196d0d66132d3091a75b54d97ccd"
-# Type="http://www.w3.org/2001/04/xmlenc#Element">
-# <xenc:EncryptionMethod
-# Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"
-# xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"/>
-# <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
-# <xenc:EncryptedKey Id="_e413a3473a60aaa6148664f3b535681f" xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
-# <xenc:EncryptionMethod
-# Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"
-# xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
-# <ds:DigestMethod
-# Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"
-# xmlns:ds="http://www.w3.org/2000/09/xmldsig#"/>
-# </xenc:EncryptionMethod>
-# <ds:KeyInfo>
-# <ds:X509Data>
-# <ds:X509Certificate>
- data = enc_assertion.encrypted_data
-def cert_from_assertion(assertion):
- """ Find certificates that are part of an assertion
-
- :param assertion: A saml.Assertion instance
- :return: possible empty list of certificates
- """
- if assertion.signature:
- if assertion.signature.key_info:
- return cert_from_key_info(assertion.signature.key_info)
- return []
-
def cert_from_key_info(key_info):
""" Get all X509 certs from a KeyInfo instance. Care is taken to make sure
that the certs are continues sequences of bytes.
@@ -107,24 +109,24 @@ def cert_from_key_info(key_info):
keys.append(cert)
return keys
-def encrypted_cert_from_key_info(key_info):
- """ Get all encrypted X509 certs from a KeyInfo instance.
- Care is taken to make sure
- that the certs are continues sequences of bytes.
+def cert_from_assertion(assertion):
+ """ Find certificates that are part of an assertion
- :param key_info: The KeyInfo instance
- :return: A possibly empty list of certs
+ :param assertion: A saml.Assertion instance
+ :return: possible empty list of certificates
"""
- keys = []
- for x509_data in key_info.x509_data:
- #print "X509Data",x509_data
- for x509_certificate in x509_data.x509_certificate:
- cert = x509_certificate.text.strip()
- cert = "".join([s.strip() for s in cert.split("\n")])
- keys.append(cert)
- return keys
+ if assertion.signature:
+ if assertion.signature.key_info:
+ return cert_from_key_info(assertion.signature.key_info)
+ return []
-def _parse_popen_output(output):
+def _parse_xmlsec_output(output):
+ """ Parse the output from xmlsec to try to find out if the
+ command was successfull or not.
+
+ :param output: The output from POpen
+ :return: A boolean; True if the command was a success otherwise False
+ """
for line in output.split("\n"):
if line == "OK":
return True
@@ -132,8 +134,14 @@ def _parse_popen_output(output):
return False
return False
-def _verify_signature(xmlsec_binary, input, der_file):
-
+def verify_signature(xmlsec_binary, input, der_file):
+ """ Verifies the signature of a XML document.
+
+ :param xmlsec_binary: The xmlsec1 binaries to be used
+ :param input: The XML document as a string
+ :param der_file: The public key that was used to sign the document
+ :return: Boolean True if the signature was correct otherwise False.
+ """
fil_p, fil = make_temp("%s" % input, decode=False)
com_list = [xmlsec_binary, "--verify",
@@ -143,7 +151,7 @@ def _verify_signature(xmlsec_binary, input, der_file):
if _TEST_:
print " ".join(com_list)
- verified = _parse_popen_output(Popen(com_list,
+ verified = _parse_xmlsec_output(Popen(com_list,
stderr=PIPE).communicate()[1])
if _TEST_:
print "Verify result: '%s'" % (verified,)
@@ -193,7 +201,7 @@ def correctly_signed_response(decoded_xml, xmlsec_binary=XMLSEC_BINARY,
verified = False
for _, der_file in certs:
- if _verify_signature(xmlsec_binary, decoded_xml, der_file):
+ if verify_signature(xmlsec_binary, decoded_xml, der_file):
verified = True
break
@@ -201,20 +209,86 @@ def correctly_signed_response(decoded_xml, xmlsec_binary=XMLSEC_BINARY,
return None
return response
+
+#----------------------------------------------------------------------------
+# SIGNATURE PART
+#----------------------------------------------------------------------------
-def sign_using_xmlsec(statement, sign_key, xmlsec_binary):
- """xmlsec1 --sign --privkey-pem test.key --id-attr:ID
- urn:oasis:names:tc:SAML:2.0:assertion:Assertion saml_response.xml"""
+def sign_assertion_using_xmlsec(statement, xmlsec_binary, key=None,
+ key_file=None):
+ """Sign a SAML statement using xmlsec.
+
+ :param statement: The statement to be signed
+ :param key: The key to be used for the signing, either this or
+ :param key_File: The file where the key can be found
+ :param xmlsec_binary: The xmlsec1 binaries used to do the signing.
+ :return: The signed statement
+ """
_, fil = make_temp("%s" % statement, decode=False)
- _, pem_file = make_temp("%s" % sign_key, ".pem")
+
+ if key:
+ _, key_file = make_temp("%s" % key, ".pem")
+ ntf = NamedTemporaryFile()
com_list = [xmlsec_binary, "--sign",
- "--privkey-cert-pem", pem_file, "--id-attr:%s" % ID_ATTR,
+ "--output", ntf.name,
+ "--privkey-pem", key_file,
+ "--id-attr:%s" % ID_ATTR,
"urn:oasis:names:tc:SAML:2.0:assertion:Assertion",
fil]
#print " ".join(com_list)
- return _parse_popen_output(Popen(com_list, stdout=PIPE).communicate()[0])
- \ No newline at end of file
+ if Popen(com_list, stdout=PIPE).communicate()[0] == "":
+ ntf.seek(0)
+ return ntf.read()
+ else:
+ raise Exception("Signing failed")
+
+PRE_SIGNATURE = {
+ "signed_info": {
+ "signature_method": {
+ "algorithm": ds.SIG_RSA_SHA1
+ },
+ "canonicalization_method": {
+ "algorithm": ds.ALG_EXC_C14N
+ },
+ "reference": {
+ # must be replace by a uriref based on the assertion ID
+ "uri": "#%s",
+ "transforms": {
+ "transform": [{
+ "algorithm": ds.TRANSFORM_ENVELOPED,
+ },
+ {
+ "algorithm": ds.ALG_EXC_C14N,
+ "inclusive_namespaces": {
+ "prefix_list": "ds saml2 saml2p xenc",
+ }
+ }
+ ]
+ },
+ "digest_method":{
+ "algorithm": ds.DIGEST_SHA1,
+ },
+ "digest_value": "",
+ }
+ },
+ "signature_value": None,
+}
+
+def pre_signature_part(id):
+ """
+ If an assertion is to be signed the signature part has to be preset
+ with to algorithms to be used, this function returns such a
+ preset part.
+
+ :param id: The identifier of the assertion, so you know which assertion
+ was signed
+ :return: A preset signature part
+ """
+
+ presig = PRE_SIGNATURE
+ presig["signed_info"]["reference"]["uri"] = "#%s" % id
+ return presig