|
|
|
|
@@ -76,7 +76,7 @@ def signed(item):
|
|
|
|
|
def get_xmlsec_binary(paths=None):
|
|
|
|
|
"""
|
|
|
|
|
Tries to find the xmlsec1 binary.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
:param paths: Non-system path paths which should be searched when
|
|
|
|
|
looking for xmlsec1
|
|
|
|
|
:return: full name of the xmlsec1 binary found. If no binaries are
|
|
|
|
|
@@ -107,12 +107,18 @@ def get_xmlsec_binary(paths=None):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
raise Exception("Can't find %s" % bin_name)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_xmlsec_cryptobackend(path=None, search_paths=None, debug=False):
|
|
|
|
|
if path is None:
|
|
|
|
|
path=get_xmlsec_binary(paths=search_paths)
|
|
|
|
|
return CryptoBackendXmlSec1(path, debug=debug)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
XMLSEC_BINARY = get_xmlsec_binary()
|
|
|
|
|
except Exception:
|
|
|
|
|
XMLSEC_BINARY = ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ID_ATTR = "ID"
|
|
|
|
|
NODE_NAME = "urn:oasis:names:tc:SAML:2.0:assertion:Assertion"
|
|
|
|
|
ENC_NODE_NAME = "urn:oasis:names:tc:SAML:2.0:assertion:EncryptedAssertion"
|
|
|
|
|
@@ -153,10 +159,10 @@ def _make_vals(val, klass, seccont, klass_inst=None, prop=None, part=False,
|
|
|
|
|
"""
|
|
|
|
|
Creates a class instance with a specified value, the specified
|
|
|
|
|
class instance may be a value on a property in a defined class instance.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
:param val: The value
|
|
|
|
|
:param klass: The value class
|
|
|
|
|
:param klass_inst: The class instance which has a property on which
|
|
|
|
|
:param klass_inst: The class instance which has a property on which
|
|
|
|
|
what this function returns is a value.
|
|
|
|
|
:param prop: The property which the value should be assigned to.
|
|
|
|
|
:param part: If the value is one of a possible list of values it should be
|
|
|
|
|
@@ -166,7 +172,7 @@ def _make_vals(val, klass, seccont, klass_inst=None, prop=None, part=False,
|
|
|
|
|
cinst = None
|
|
|
|
|
|
|
|
|
|
#print "make_vals(%s, %s)" % (val, klass)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if isinstance(val, dict):
|
|
|
|
|
cinst = _instance(klass, val, seccont, base64encode=base64encode,
|
|
|
|
|
elements_to_sign=elements_to_sign)
|
|
|
|
|
@@ -175,23 +181,23 @@ def _make_vals(val, klass, seccont, klass_inst=None, prop=None, part=False,
|
|
|
|
|
cinst = klass().set_text(val)
|
|
|
|
|
except ValueError:
|
|
|
|
|
if not part:
|
|
|
|
|
cis = [_make_vals(sval, klass, seccont, klass_inst, prop,
|
|
|
|
|
cis = [_make_vals(sval, klass, seccont, klass_inst, prop,
|
|
|
|
|
True, base64encode, elements_to_sign) for sval in val]
|
|
|
|
|
setattr(klass_inst, prop, cis)
|
|
|
|
|
else:
|
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if part:
|
|
|
|
|
return cinst
|
|
|
|
|
else:
|
|
|
|
|
if cinst:
|
|
|
|
|
else:
|
|
|
|
|
if cinst:
|
|
|
|
|
cis = [cinst]
|
|
|
|
|
setattr(klass_inst, prop, cis)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _instance(klass, ava, seccont, base64encode=False, elements_to_sign=None):
|
|
|
|
|
instance = klass()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for prop in instance.c_attributes.values():
|
|
|
|
|
#print "# %s" % (prop)
|
|
|
|
|
if prop in ava:
|
|
|
|
|
@@ -204,7 +210,7 @@ def _instance(klass, ava, seccont, base64encode=False, elements_to_sign=None):
|
|
|
|
|
|
|
|
|
|
if "text" in ava:
|
|
|
|
|
instance.set_text(ava["text"], base64encode)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for prop, klassdef in instance.c_children.values():
|
|
|
|
|
#print "## %s, %s" % (prop, klassdef)
|
|
|
|
|
if prop in ava:
|
|
|
|
|
@@ -223,14 +229,14 @@ def _instance(klass, ava, seccont, base64encode=False, elements_to_sign=None):
|
|
|
|
|
for item in ava["extension_elements"]:
|
|
|
|
|
instance.extension_elements.append(
|
|
|
|
|
ExtensionElement(item["tag"]).loadd(item))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if "extension_attributes" in ava:
|
|
|
|
|
for key, val in ava["extension_attributes"].items():
|
|
|
|
|
instance.extension_attributes[key] = val
|
|
|
|
|
|
|
|
|
|
if "signature" in ava:
|
|
|
|
|
elements_to_sign.append((class_name(instance), instance.id))
|
|
|
|
|
|
|
|
|
|
elements_to_sign.append((class_name(instance), instance.id))
|
|
|
|
|
|
|
|
|
|
return instance
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -246,7 +252,7 @@ def signed_instance_factory(instance, seccont, elements_to_sign=None):
|
|
|
|
|
signed_xml = "%s" % instance
|
|
|
|
|
for (node_name, nodeid) in elements_to_sign:
|
|
|
|
|
signed_xml = seccont.sign_statement_using_xmlsec(
|
|
|
|
|
signed_xml, klass_namn=node_name, nodeid=nodeid)
|
|
|
|
|
signed_xml, class_name=node_name, node_id=nodeid)
|
|
|
|
|
|
|
|
|
|
#print "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
|
|
|
|
#print "%s" % signed_xml
|
|
|
|
|
@@ -259,29 +265,29 @@ def signed_instance_factory(instance, seccont, elements_to_sign=None):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_id():
|
|
|
|
|
""" Create a string of 40 random characters from the set [a-p],
|
|
|
|
|
""" 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 += chr(random.randint(0, 15) + ord('a'))
|
|
|
|
|
return ret
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def make_temp(string, suffix="", decode=True):
|
|
|
|
|
""" 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
|
|
|
|
|
: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
|
|
|
|
|
:param decode: The input string might be base64 coded. If so it
|
|
|
|
|
must, in some cases, be decoded before being placed in the file.
|
|
|
|
|
:return: 2-tuple with file pointer ( so the calling function can
|
|
|
|
|
close the file) and filename (which is for instance needed by the
|
|
|
|
|
close the file) and filename (which is for instance needed by the
|
|
|
|
|
xmlsec function).
|
|
|
|
|
"""
|
|
|
|
|
ntf = NamedTemporaryFile(suffix=suffix)
|
|
|
|
|
@@ -460,15 +466,15 @@ def x509_rsa_loads(string):
|
|
|
|
|
def pem_format(key):
|
|
|
|
|
return "\n".join(["-----BEGIN CERTIFICATE-----",
|
|
|
|
|
key, "-----END CERTIFICATE-----"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def parse_xmlsec_output(output):
|
|
|
|
|
""" Parse the output from xmlsec to try to find out if the
|
|
|
|
|
""" 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
|
|
|
|
|
@@ -476,8 +482,6 @@ def parse_xmlsec_output(output):
|
|
|
|
|
raise XmlsecError(output)
|
|
|
|
|
raise XmlsecError(output)
|
|
|
|
|
|
|
|
|
|
__DEBUG = 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class BadSignature(Exception):
|
|
|
|
|
"""The signature is invalid."""
|
|
|
|
|
@@ -559,7 +563,7 @@ def verify_signature(enctext, xmlsec_binary, cert_file=None, cert_type="pem",
|
|
|
|
|
""" Verifies the signature of a XML document.
|
|
|
|
|
|
|
|
|
|
:param enctext: The signed XML document
|
|
|
|
|
:param xmlsec_binary: The xmlsec1 binaries to be used
|
|
|
|
|
:param xmlsec_binary: The xmlsec1 binaries to be used (or CryptoBackend())
|
|
|
|
|
:param cert_file: The public key used to decrypt the signature
|
|
|
|
|
:param cert_type: The cert format
|
|
|
|
|
:param node_name: The SAML class of the root node in the signed document
|
|
|
|
|
@@ -572,46 +576,15 @@ def verify_signature(enctext, xmlsec_binary, cert_file=None, cert_type="pem",
|
|
|
|
|
if not id_attr:
|
|
|
|
|
id_attr = ID_ATTR
|
|
|
|
|
|
|
|
|
|
_, fil = make_temp(enctext, decode=False)
|
|
|
|
|
|
|
|
|
|
com_list = [xmlsec_binary, "--verify",
|
|
|
|
|
"--pubkey-cert-%s" % cert_type, cert_file,
|
|
|
|
|
"--id-attr:%s" % id_attr, node_name]
|
|
|
|
|
|
|
|
|
|
if debug:
|
|
|
|
|
com_list.append("--store-signatures")
|
|
|
|
|
|
|
|
|
|
if node_id:
|
|
|
|
|
com_list.extend(["--node-id", node_id])
|
|
|
|
|
|
|
|
|
|
com_list.append(fil)
|
|
|
|
|
crypto = xmlsec_binary
|
|
|
|
|
if not isinstance(crypto, CryptoBackend):
|
|
|
|
|
# backwards compatibility
|
|
|
|
|
crypto = CryptoBackendXmlSec1(xmlsec_binary, debug=debug)
|
|
|
|
|
|
|
|
|
|
logger.debug("com_list: %s" % com_list)
|
|
|
|
|
|
|
|
|
|
if __DEBUG:
|
|
|
|
|
try:
|
|
|
|
|
print " ".join(com_list)
|
|
|
|
|
except TypeError:
|
|
|
|
|
print "cert_type", cert_type
|
|
|
|
|
print "cert_file", cert_file
|
|
|
|
|
print "node_name", node_name
|
|
|
|
|
print "fil", fil
|
|
|
|
|
raise
|
|
|
|
|
print "%s: %s" % (cert_file, os.access(cert_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:
|
|
|
|
|
logger.error(LOG_LINE % (p_out, exc))
|
|
|
|
|
raise SignatureError("%s" % (exc,))
|
|
|
|
|
|
|
|
|
|
return verified
|
|
|
|
|
return crypto.validate_signature(enctext, cert_file=cert_file,
|
|
|
|
|
cert_type=cert_type, node_name=node_name,
|
|
|
|
|
node_id=node_id, id_attr=id_attr,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
@@ -626,7 +599,7 @@ def read_cert_from_file(cert_file, cert_type):
|
|
|
|
|
"""
|
|
|
|
|
if not cert_file:
|
|
|
|
|
return ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if cert_type == "pem":
|
|
|
|
|
line = open(cert_file).read().split("\n")
|
|
|
|
|
if line[0] == "-----BEGIN CERTIFICATE-----":
|
|
|
|
|
@@ -652,6 +625,148 @@ def read_cert_from_file(cert_file, cert_type):
|
|
|
|
|
return base64.b64encode(str(data))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CryptoBackend():
|
|
|
|
|
|
|
|
|
|
def __init__(self, debug=False):
|
|
|
|
|
self.debug = debug
|
|
|
|
|
|
|
|
|
|
def encrypt(self, text, recv_key, template, key_type):
|
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
|
|
def decrypt(self, enctext, key_file):
|
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
|
|
def sign_statement(self, statement, class_name, key, key_file, nodeid,
|
|
|
|
|
id_attr):
|
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
|
|
def validate_signature(self, enctext, cert_file, cert_type, node_name,
|
|
|
|
|
node_id, id_attr):
|
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CryptoBackendXmlSec1(CryptoBackend):
|
|
|
|
|
|
|
|
|
|
__DEBUG = 0
|
|
|
|
|
|
|
|
|
|
def __init__(self, xmlsec_binary, **kwargs):
|
|
|
|
|
CryptoBackend.__init__(self, **kwargs)
|
|
|
|
|
assert(isinstance(xmlsec_binary, basestring))
|
|
|
|
|
self.xmlsec = xmlsec_binary
|
|
|
|
|
|
|
|
|
|
def encrypt(self, text, recv_key, template, key_type):
|
|
|
|
|
logger.info("Encryption input len: %d" % len(text))
|
|
|
|
|
_, fil = make_temp("%s" % text, decode=False)
|
|
|
|
|
|
|
|
|
|
com_list = [self.xmlsec, "--encrypt", "--pubkey-pem", recv_key,
|
|
|
|
|
"--session-key", key_type, "--xml-data", fil,
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
(_stdout, _stderr, output) = self._run_xmlsec(com_list, [template],
|
|
|
|
|
exception=DecryptError)
|
|
|
|
|
return output
|
|
|
|
|
|
|
|
|
|
def decrypt(self, enctext, key_file):
|
|
|
|
|
logger.info("Decrypt input len: %d" % len(enctext))
|
|
|
|
|
_, fil = make_temp("%s" % enctext, decode=False)
|
|
|
|
|
|
|
|
|
|
com_list = [self.xmlsec, "--decrypt", "--privkey-pem",
|
|
|
|
|
key_file, "--id-attr:%s" % ID_ATTR, ENC_KEY_CLASS,
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
(_stdout, _stderr, output) = self._run_xmlsec(com_list, [fil],
|
|
|
|
|
exception=DecryptError)
|
|
|
|
|
return output
|
|
|
|
|
|
|
|
|
|
def sign_statement(self, statement, class_name, key, key_file, node_id,
|
|
|
|
|
id_attr):
|
|
|
|
|
|
|
|
|
|
_, fil = make_temp("%s" % statement, decode=False)
|
|
|
|
|
|
|
|
|
|
com_list = [self.xmlsec, "--sign",
|
|
|
|
|
"--privkey-pem", key_file,
|
|
|
|
|
"--id-attr:%s" % id_attr, class_name,
|
|
|
|
|
#"--store-signatures"
|
|
|
|
|
]
|
|
|
|
|
if node_id:
|
|
|
|
|
com_list.extend(["--node-id", node_id])
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
(stdout, stderr, signed_statement) = \
|
|
|
|
|
self._run_xmlsec(com_list, [fil], validate_output=False)
|
|
|
|
|
# this doesn't work if --store-signatures are used
|
|
|
|
|
if stdout == "":
|
|
|
|
|
if signed_statement:
|
|
|
|
|
return signed_statement
|
|
|
|
|
logger.error("Signing operation failed :\nstdout : %s\nstderr : %s" \
|
|
|
|
|
% (stdout, stderr))
|
|
|
|
|
raise Exception("Signing failed")
|
|
|
|
|
except DecryptError, exc:
|
|
|
|
|
raise Exception("Signing failed")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def validate_signature(self, enctext, cert_file, cert_type, node_name,
|
|
|
|
|
node_id, id_attr):
|
|
|
|
|
_, fil = make_temp(enctext, decode=False)
|
|
|
|
|
|
|
|
|
|
com_list = [self.xmlsec, "--verify",
|
|
|
|
|
"--pubkey-cert-%s" % cert_type, cert_file,
|
|
|
|
|
"--id-attr:%s" % id_attr, node_name]
|
|
|
|
|
|
|
|
|
|
if self.debug:
|
|
|
|
|
com_list.append("--store-signatures")
|
|
|
|
|
|
|
|
|
|
if node_id:
|
|
|
|
|
com_list.extend(["--node-id", node_id])
|
|
|
|
|
|
|
|
|
|
if self.__DEBUG:
|
|
|
|
|
try:
|
|
|
|
|
print " ".join(com_list)
|
|
|
|
|
except TypeError:
|
|
|
|
|
print "cert_type", cert_type
|
|
|
|
|
print "cert_file", cert_file
|
|
|
|
|
print "node_name", node_name
|
|
|
|
|
print "fil", fil
|
|
|
|
|
raise
|
|
|
|
|
print "%s: %s" % (cert_file, os.access(cert_file, os.F_OK))
|
|
|
|
|
print "%s: %s" % (fil, os.access(fil, os.F_OK))
|
|
|
|
|
|
|
|
|
|
(_stdout, stderr, _output) = self._run_xmlsec(com_list, [fil],
|
|
|
|
|
exception=SignatureError)
|
|
|
|
|
return parse_xmlsec_output(stderr)
|
|
|
|
|
|
|
|
|
|
def _run_xmlsec(self, com_list, extra_args, validate_output=True,
|
|
|
|
|
exception=XmlsecError):
|
|
|
|
|
"""
|
|
|
|
|
Common code to invoke xmlsec and parse the output.
|
|
|
|
|
:param com_list: Key-value parameter list for xmlsec
|
|
|
|
|
:param extra_args: Positional parameters to be appended after all
|
|
|
|
|
key-value parameters
|
|
|
|
|
:param validate_output: Parse and validate the output
|
|
|
|
|
:param exception: The exception class to raise on errors
|
|
|
|
|
:result: Whatever xmlsec wrote to an --output temporary file
|
|
|
|
|
"""
|
|
|
|
|
ntf = NamedTemporaryFile()
|
|
|
|
|
com_list.extend(["--output", ntf.name])
|
|
|
|
|
com_list += extra_args
|
|
|
|
|
|
|
|
|
|
logger.debug("xmlsec command: %s" % " ".join(com_list))
|
|
|
|
|
|
|
|
|
|
pof = Popen(com_list, stderr=PIPE, stdout=PIPE)
|
|
|
|
|
|
|
|
|
|
p_out = pof.stdout.read()
|
|
|
|
|
p_err = pof.stderr.read()
|
|
|
|
|
try:
|
|
|
|
|
if validate_output:
|
|
|
|
|
parse_xmlsec_output(p_err)
|
|
|
|
|
except XmlsecError, exc:
|
|
|
|
|
logger.error(LOG_LINE_2 % (p_out, p_err, exc))
|
|
|
|
|
raise exception("%s" % (exc,))
|
|
|
|
|
|
|
|
|
|
ntf.seek(0)
|
|
|
|
|
return (p_out, p_err, ntf.read())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def security_context(conf, debug=None):
|
|
|
|
|
""" Creates a security context based on the configuration
|
|
|
|
|
|
|
|
|
|
@@ -660,7 +775,7 @@ def security_context(conf, debug=None):
|
|
|
|
|
"""
|
|
|
|
|
if not conf:
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if debug is None:
|
|
|
|
|
debug = conf.debug
|
|
|
|
|
|
|
|
|
|
@@ -670,38 +785,41 @@ def security_context(conf, debug=None):
|
|
|
|
|
if _only_md is None:
|
|
|
|
|
_only_md = False
|
|
|
|
|
|
|
|
|
|
return SecurityContext(conf.xmlsec_binary, conf.key_file,
|
|
|
|
|
crypto = get_xmlsec_cryptobackend(conf.xmlsec_binary, debug=debug)
|
|
|
|
|
|
|
|
|
|
return SecurityContext(crypto, conf.key_file,
|
|
|
|
|
cert_file=conf.cert_file, metadata=metadata,
|
|
|
|
|
debug=debug, only_use_keys_in_metadata=_only_md)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SecurityContext(object):
|
|
|
|
|
def __init__(self, xmlsec_binary, key_file="", key_type="pem",
|
|
|
|
|
def __init__(self, crypto, key_file="", key_type="pem",
|
|
|
|
|
cert_file="", cert_type="pem", metadata=None,
|
|
|
|
|
debug=False, template="", encrypt_key_type="des-192",
|
|
|
|
|
only_use_keys_in_metadata=False):
|
|
|
|
|
|
|
|
|
|
self.xmlsec = xmlsec_binary
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.crypto = crypto
|
|
|
|
|
assert (isinstance(self.crypto, CryptoBackend))
|
|
|
|
|
|
|
|
|
|
# Your private key
|
|
|
|
|
self.key_file = key_file
|
|
|
|
|
self.key_type = key_type
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Your public key
|
|
|
|
|
self.cert_file = cert_file
|
|
|
|
|
self.cert_type = cert_type
|
|
|
|
|
self.my_cert = read_cert_from_file(cert_file, cert_type)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.metadata = metadata
|
|
|
|
|
self.only_use_keys_in_metadata = only_use_keys_in_metadata
|
|
|
|
|
self.debug = debug
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if not template:
|
|
|
|
|
this_dir, this_filename = os.path.split(__file__)
|
|
|
|
|
self.template = os.path.join(this_dir, "xml", "template.xml")
|
|
|
|
|
else:
|
|
|
|
|
self.template = template
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.key_type = encrypt_key_type
|
|
|
|
|
|
|
|
|
|
def correctly_signed(self, xml, must=False):
|
|
|
|
|
@@ -725,63 +843,20 @@ class SecurityContext(object):
|
|
|
|
|
if not template:
|
|
|
|
|
template = self.template
|
|
|
|
|
|
|
|
|
|
logger.info("input len: %d" % len(text))
|
|
|
|
|
_, fil = make_temp("%s" % text, decode=False)
|
|
|
|
|
ntf = NamedTemporaryFile()
|
|
|
|
|
|
|
|
|
|
com_list = [self.xmlsec, "--encrypt", "--pubkey-pem", recv_key,
|
|
|
|
|
"--session-key", key_type, "--xml-data", fil,
|
|
|
|
|
"--output", ntf.name, template]
|
|
|
|
|
|
|
|
|
|
logger.debug("Encryption command: %s" % " ".join(com_list))
|
|
|
|
|
|
|
|
|
|
pof = Popen(com_list, stderr=PIPE, stdout=PIPE)
|
|
|
|
|
|
|
|
|
|
p_err = pof.stderr.read()
|
|
|
|
|
try:
|
|
|
|
|
parse_xmlsec_output(p_err)
|
|
|
|
|
except XmlsecError, exc:
|
|
|
|
|
p_out = pof.stdout.read()
|
|
|
|
|
logger.error(LOG_LINE_2 % (p_out, p_err, exc))
|
|
|
|
|
raise DecryptError("%s" % (exc,))
|
|
|
|
|
|
|
|
|
|
ntf.seek(0)
|
|
|
|
|
return ntf.read()
|
|
|
|
|
return self.crypto.encrypt(text, recv_key, template, key_type)
|
|
|
|
|
|
|
|
|
|
def decrypt(self, enctext):
|
|
|
|
|
""" Decrypting an encrypted text by the use of a private key.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
:param enctext: The encrypted text as a string
|
|
|
|
|
:return: The decrypted text
|
|
|
|
|
"""
|
|
|
|
|
return self.crypto.decrypt(enctext, self.key_file)
|
|
|
|
|
|
|
|
|
|
logger.info("input len: %d" % len(enctext))
|
|
|
|
|
_, fil = make_temp("%s" % enctext, decode=False)
|
|
|
|
|
ntf = NamedTemporaryFile()
|
|
|
|
|
|
|
|
|
|
com_list = [self.xmlsec, "--decrypt", "--privkey-pem",
|
|
|
|
|
self.key_file, "--output", ntf.name,
|
|
|
|
|
"--id-attr:%s" % ID_ATTR, ENC_KEY_CLASS, fil]
|
|
|
|
|
|
|
|
|
|
logger.debug("Decrypt command: %s" % " ".join(com_list))
|
|
|
|
|
|
|
|
|
|
pof = Popen(com_list, stderr=PIPE, stdout=PIPE)
|
|
|
|
|
|
|
|
|
|
p_err = pof.stderr.read()
|
|
|
|
|
try:
|
|
|
|
|
parse_xmlsec_output(p_err)
|
|
|
|
|
except XmlsecError, exc:
|
|
|
|
|
p_out = pof.stdout.read()
|
|
|
|
|
logger.error(LOG_LINE_2 % (p_out, p_err, exc))
|
|
|
|
|
raise DecryptError("%s" % (exc,))
|
|
|
|
|
|
|
|
|
|
ntf.seek(0)
|
|
|
|
|
return ntf.read()
|
|
|
|
|
|
|
|
|
|
def verify_signature(self, enctext, cert_file=None, cert_type="pem",
|
|
|
|
|
def verify_signature(self, enctext, cert_file=None, cert_type="pem",
|
|
|
|
|
node_name=NODE_NAME, node_id=None, id_attr=""):
|
|
|
|
|
""" Verifies the signature of a XML document.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
:param enctext: The XML document as a string
|
|
|
|
|
:param cert_file: The public key that was used to sign the document
|
|
|
|
|
:param cert_type: The file type of the certificate
|
|
|
|
|
@@ -795,10 +870,10 @@ class SecurityContext(object):
|
|
|
|
|
if not cert_file:
|
|
|
|
|
cert_file = self.cert_file
|
|
|
|
|
cert_type = self.cert_type
|
|
|
|
|
|
|
|
|
|
return verify_signature(enctext, self.xmlsec, cert_file, cert_type,
|
|
|
|
|
|
|
|
|
|
return verify_signature(enctext, self.crypto, cert_file, cert_type,
|
|
|
|
|
node_name, self.debug, node_id, id_attr)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _check_signature(self, decoded_xml, item, node_name=NODE_NAME,
|
|
|
|
|
origdoc=None, id_attr="", must=False):
|
|
|
|
|
#print item
|
|
|
|
|
@@ -829,12 +904,12 @@ class SecurityContext(object):
|
|
|
|
|
else:
|
|
|
|
|
logger.debug("==== Certs from metadata ==== %s: %s ====" % (issuer,
|
|
|
|
|
certs))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if not certs:
|
|
|
|
|
raise MissingKey("%s" % issuer)
|
|
|
|
|
|
|
|
|
|
#print certs
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
verified = False
|
|
|
|
|
for _, pem_file in certs:
|
|
|
|
|
try:
|
|
|
|
|
@@ -984,14 +1059,14 @@ class SecurityContext(object):
|
|
|
|
|
|
|
|
|
|
def correctly_signed_response(self, decoded_xml, must=False, origdoc=None):
|
|
|
|
|
""" 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
|
|
|
|
|
the message if any.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
:param decoded_xml: The SAML message as a XML string
|
|
|
|
|
:param must: Whether there must be a signature
|
|
|
|
|
:return: None if the signature can not be verified otherwise an instance
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
response = samlp.any_response_from_string(decoded_xml)
|
|
|
|
|
if not response:
|
|
|
|
|
raise TypeError("Not a Response")
|
|
|
|
|
@@ -1017,16 +1092,20 @@ class SecurityContext(object):
|
|
|
|
|
except Exception, exc:
|
|
|
|
|
logger.error("correctly_signed_response: %s" % exc)
|
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
|
#--------------------------------------------------------------------------
|
|
|
|
|
# SIGNATURE PART
|
|
|
|
|
#--------------------------------------------------------------------------
|
|
|
|
|
def sign_statement_using_xmlsec(self, statement, klass_namn, key=None,
|
|
|
|
|
key_file=None, nodeid=None, id_attr=""):
|
|
|
|
|
"""Sign a SAML statement using xmlsec.
|
|
|
|
|
|
|
|
|
|
def sign_statement_using_xmlsec(self, statement, **kwargs):
|
|
|
|
|
""" Deprecated function. See sign_statement(). """
|
|
|
|
|
return self.sign_statement(statement, **kwargs)
|
|
|
|
|
|
|
|
|
|
def sign_statement(self, statement, class_name, key=None,
|
|
|
|
|
key_file=None, node_id=None, id_attr=""):
|
|
|
|
|
"""Sign a SAML statement.
|
|
|
|
|
|
|
|
|
|
: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
|
|
|
|
|
@@ -1034,7 +1113,6 @@ class SecurityContext(object):
|
|
|
|
|
'id','Id' or 'ID'
|
|
|
|
|
:return: The signed statement
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
if not id_attr:
|
|
|
|
|
id_attr = ID_ATTR
|
|
|
|
|
|
|
|
|
|
@@ -1043,68 +1121,40 @@ class SecurityContext(object):
|
|
|
|
|
|
|
|
|
|
if not key and not key_file:
|
|
|
|
|
key_file = self.key_file
|
|
|
|
|
|
|
|
|
|
_, fil = make_temp("%s" % statement, decode=False)
|
|
|
|
|
|
|
|
|
|
ntf = NamedTemporaryFile()
|
|
|
|
|
return self.crypto.sign_statement(statement, class_name, key, key_file,
|
|
|
|
|
node_id, id_attr)
|
|
|
|
|
|
|
|
|
|
com_list = [self.xmlsec, "--sign",
|
|
|
|
|
"--output", ntf.name,
|
|
|
|
|
"--privkey-pem", key_file,
|
|
|
|
|
"--id-attr:%s" % id_attr, klass_namn
|
|
|
|
|
#"--store-signatures"
|
|
|
|
|
]
|
|
|
|
|
if nodeid:
|
|
|
|
|
com_list.extend(["--node-id", nodeid])
|
|
|
|
|
def sign_assertion_using_xmlsec(self, statement, **kwargs):
|
|
|
|
|
""" Deprecated function. See sign_assertion(). """
|
|
|
|
|
return self.sign_statement(statement, class_name(saml.Assertion()),
|
|
|
|
|
**kwargs)
|
|
|
|
|
|
|
|
|
|
com_list.append(fil)
|
|
|
|
|
def sign_assertion(self, statement, **kwargs):
|
|
|
|
|
"""Sign a SAML assertion.
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
signed_statement = ntf.read()
|
|
|
|
|
if not signed_statement:
|
|
|
|
|
print >> sys.stderr, p_err
|
|
|
|
|
raise Exception("Signing failed")
|
|
|
|
|
else:
|
|
|
|
|
return signed_statement
|
|
|
|
|
else:
|
|
|
|
|
print >> sys.stderr, p_out
|
|
|
|
|
print "E", p_err
|
|
|
|
|
raise Exception("Signing failed")
|
|
|
|
|
|
|
|
|
|
def sign_assertion_using_xmlsec(self, statement, key=None, key_file=None,
|
|
|
|
|
nodeid=None, id_attr=""):
|
|
|
|
|
"""Sign a SAML assertion 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
|
|
|
|
|
:return: The signed statement
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
return self.sign_statement_using_xmlsec(statement,
|
|
|
|
|
class_name(saml.Assertion()),
|
|
|
|
|
key, key_file, nodeid,
|
|
|
|
|
id_attr=id_attr)
|
|
|
|
|
|
|
|
|
|
def sign_attribute_query_using_xmlsec(self, statement, key=None,
|
|
|
|
|
key_file=None, nodeid=None,
|
|
|
|
|
id_attr=""):
|
|
|
|
|
"""Sign a SAML assertion using xmlsec.
|
|
|
|
|
See sign_statement() for the kwargs.
|
|
|
|
|
|
|
|
|
|
: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
|
|
|
|
|
:return: The signed statement
|
|
|
|
|
"""
|
|
|
|
|
return self.sign_statement(statement, class_name(saml.Assertion()),
|
|
|
|
|
**kwargs)
|
|
|
|
|
|
|
|
|
|
return self.sign_statement_using_xmlsec(statement, class_name(
|
|
|
|
|
samlp.AttributeQuery()), key, key_file, nodeid, id_attr=id_attr)
|
|
|
|
|
def sign_attribute_query_using_xmlsec(self, statement, **kwargs):
|
|
|
|
|
""" Deprecated function. See sign_attribute_query(). """
|
|
|
|
|
return self.sign_attribute_query(statement, **kwargs);
|
|
|
|
|
|
|
|
|
|
def sign_attribute_query(self, statement, **kwargs):
|
|
|
|
|
"""Sign a SAML attribute query.
|
|
|
|
|
|
|
|
|
|
See sign_statement() for the kwargs.
|
|
|
|
|
|
|
|
|
|
:param statement: The statement to be signed
|
|
|
|
|
:return: The signed statement
|
|
|
|
|
"""
|
|
|
|
|
return self.sign_statement(statement, class_name(
|
|
|
|
|
samlp.AttributeQuery()), **kwargs)
|
|
|
|
|
|
|
|
|
|
def multiple_signatures(self, statement, to_sign, key=None, key_file=None):
|
|
|
|
|
"""
|
|
|
|
|
@@ -1126,15 +1176,13 @@ class SecurityContext(object):
|
|
|
|
|
|
|
|
|
|
if not item.signature:
|
|
|
|
|
item.signature = pre_signature_part(sid, self.cert_file)
|
|
|
|
|
|
|
|
|
|
statement = self.sign_statement_using_xmlsec(statement,
|
|
|
|
|
class_name(item),
|
|
|
|
|
key=key,
|
|
|
|
|
key_file=key_file,
|
|
|
|
|
nodeid=id,
|
|
|
|
|
id_attr=id_attr)
|
|
|
|
|
|
|
|
|
|
statement = self.sign_statement(statement, class_name(item),
|
|
|
|
|
key=key, key_file=key_file,
|
|
|
|
|
node_id=id, id_attr=id_attr)
|
|
|
|
|
return statement
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ===========================================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1143,7 +1191,7 @@ def pre_signature_part(ident, public_key=None, identifier=None):
|
|
|
|
|
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
|
|
|
|
|
:param public_key: The base64 part of a PEM file
|
|
|
|
|
@@ -1157,26 +1205,26 @@ def pre_signature_part(ident, public_key=None, identifier=None):
|
|
|
|
|
trans1 = ds.Transform(algorithm=ds.ALG_EXC_C14N)
|
|
|
|
|
transforms = ds.Transforms(transform=[trans0, trans1])
|
|
|
|
|
digest_method = ds.DigestMethod(algorithm=ds.DIGEST_SHA1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
reference = ds.Reference(uri="#%s" % ident, digest_value=ds.DigestValue(),
|
|
|
|
|
transforms=transforms, digest_method=digest_method)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
signed_info = ds.SignedInfo(signature_method=signature_method,
|
|
|
|
|
canonicalization_method=canonicalization_method,
|
|
|
|
|
reference=reference)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
signature = ds.Signature(signed_info=signed_info,
|
|
|
|
|
signature_value=ds.SignatureValue())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if identifier:
|
|
|
|
|
signature.id = "Signature%d" % identifier
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if public_key:
|
|
|
|
|
x509_data = ds.X509Data(
|
|
|
|
|
x509_certificate=[ds.X509Certificate(text=public_key)])
|
|
|
|
|
key_info = ds.KeyInfo(x509_data=x509_data)
|
|
|
|
|
signature.key_info = key_info
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return signature
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|