From 367c9713daa67e212454bae8341921ba568d7012 Mon Sep 17 00:00:00 2001 From: Josh Karlin Date: Tue, 30 Mar 2010 21:03:05 +0000 Subject: [PATCH] This is totally busted code, in the midst of adding support for XML credentials. Noone else uses this branch but me so I figure that's okay... --- sfa/client/sfi.py | 2 +- sfa/managers/registry_manager_pl.py | 2 +- sfa/plc/api.py | 2 +- sfa/trust/credential.py | 216 +++++++++++++++++++++--- sfa/trust/credential_legacy.py | 247 ++++++++++++++++++++++++++++ sfa/trust/hierarchy.py | 4 +- 6 files changed, 447 insertions(+), 26 deletions(-) create mode 100644 sfa/trust/credential_legacy.py diff --git a/sfa/client/sfi.py b/sfa/client/sfi.py index 0f46a4a4..0e3b8121 100755 --- a/sfa/client/sfi.py +++ b/sfa/client/sfi.py @@ -478,7 +478,7 @@ class Sfi: dcred.set_privileges(user_cred.get_privileges()) dcred.set_delegate(True) dcred.set_pubkey(object_gid.get_pubkey()) - dcred.set_issuer(user_key, user_hrn) + dcred.set_issuer(user_key, user_cred.get_gid_caller()) dcred.set_parent(user_cred) dcred.encode() dcred.sign() diff --git a/sfa/managers/registry_manager_pl.py b/sfa/managers/registry_manager_pl.py index 7c969278..03462ee5 100644 --- a/sfa/managers/registry_manager_pl.py +++ b/sfa/managers/registry_manager_pl.py @@ -55,7 +55,7 @@ def get_credential(api, xrn, type, is_self=False): new_cred = Credential(subject = object_gid.get_subject()) new_cred.set_gid_caller(caller_gid) new_cred.set_gid_object(object_gid) - new_cred.set_issuer(key=auth_info.get_pkey_object(), subject=auth_hrn) + new_cred.set_issuer_keys(auth_info.get_pkey_object(), auth_info.get_gid_object()) new_cred.set_pubkey(object_gid.get_pubkey()) new_cred.set_privileges(rights) new_cred.set_delegate(True) diff --git a/sfa/plc/api.py b/sfa/plc/api.py index 52f72ffa..4cb05b13 100644 --- a/sfa/plc/api.py +++ b/sfa/plc/api.py @@ -153,7 +153,7 @@ class SfaAPI(BaseAPI): new_cred = Credential(subject = object_gid.get_subject()) new_cred.set_gid_caller(object_gid) new_cred.set_gid_object(object_gid) - new_cred.set_issuer(key=auth_info.get_pkey_object(), subject=auth_hrn) + new_cred.set_issuer_keys(auth_info.get_pkey_object(), auth_info.get_gid_object()) new_cred.set_pubkey(object_gid.get_pubkey()) r1 = determine_rights(type, hrn) new_cred.set_privileges(r1) diff --git a/sfa/trust/credential.py b/sfa/trust/credential.py index 0bdc07de..5b27d2d4 100644 --- a/sfa/trust/credential.py +++ b/sfa/trust/credential.py @@ -9,20 +9,88 @@ ### $URL$ import xmlrpclib +import random +import os + +import xml.dom.minidom +from xml.dom.minidom import Document +from sfa.trust.credential_legacy import CredentialLegacy from sfa.trust.certificate import Certificate from sfa.trust.rights import * from sfa.trust.gid import * from sfa.util.faults import * from sfa.util.sfalogging import * + +signed_cred_template = \ +''' + + %s + + + %s + + +''' + + +credential_template = \ +''' + + privilege + 8 + %s + %s + %s + %s + + %s + + %s + + +''' + + +signature_template = \ +''' + + + + + + + + + + + + + + + + + + + + + + +''' + + + + + ## # Credential is a tuple: -# (GIDCaller, GIDObject, LifeTime, Privileges, Delegate) +# (GIDCaller, GIDObject, LifeTime, Privileges, DelegateBit) # -# These fields are encoded using xmlrpc into the subjectAltName field of the -# x509 certificate. Note: Call encode() once the fields have been filled in -# to perform this encoding. +# These fields are encoded in one of two ways. The legacy style places +# it in the subjectAltName of an X509 certificate. The new credentials +# are placed in signed XML. + class Credential(Certificate): gidCaller = None @@ -30,6 +98,11 @@ class Credential(Certificate): lifeTime = None privileges = None delegate = False + issuer_key = None + issuer_gid = None + issuer_pubkey = None + parent = None + xml = None ## # Create a Credential object @@ -40,7 +113,46 @@ class Credential(Certificate): # @param filename If filename!=None, load the credential from the file def __init__(self, create=False, subject=None, string=None, filename=None): - Certificate.__init__(self, create, subject, string, filename) + + # Check if this is a legacy credential, translate it if so + if string or filename: + if str: + str = string + elif filename: + str = file(filename).read() + + if str.strip().startswith("-----"): + self.translate_legacy(str) + + + ## + # Translate a legacy credential into a new one + # + # @param String of the legacy credential + + def translate_legacy(self, str): + legacy = CredentialLegacy(create, subject, string, filename) + self.gidCaller = legacy.get_gid_caller() + self.gidObject = legacy.get_gid_object() + self.lifeTime = legacy.get_lifetime() + self.privileges = legacy.get_privileges() + self.delegate = legacy.get_delegate() + self.encode() + + ## + # Need the issuer's private key and name + # @param key Keypair object containing the private key of the issuer + # @param gid GID of the issuing authority + + def set_issuer_keys(self, privkey, gid): + self.issuer_key = privkey + self.issuer_gid = gid + + def set_pubkey(self, pubkey): + self.issuer_pubkey = pubkey + + def set_parent(self, cred): + self.parent = cred ## # set the GID of the caller @@ -139,26 +251,88 @@ class Credential(Certificate): return False return rights.can_perform(op_name) + def append_sub(self, doc, parent, element, text): + parent.appendChild(doc.createElement(element).appendChild(doc.createTextNode(text))) + ## - # Encode the attributes of the credential into a string and store that - # string in the alt-subject-name field of the X509 object. This should be - # done immediately before signing the credential. + # Encode the attributes of the credential into an XML string + # This should be done immediately before signing the credential. def encode(self): - dict = {"gidCaller": None, - "gidObject": None, - "lifeTime": self.lifeTime, - "privileges": None, - "delegate": self.delegate} - if self.gidCaller: - dict["gidCaller"] = self.gidCaller.save_to_string(save_parents=True) - if self.gidObject: - dict["gidObject"] = self.gidObject.save_to_string(save_parents=True) - if self.privileges: - dict["privileges"] = self.privileges.save_to_string() - str = xmlrpclib.dumps((dict,), allow_none=True) - self.set_data('URI:http://' + str) + # Get information from the parent credential + if self.parent: + p_doc = xml.dom.minidom.parseString(self.parent) + p_signed_cred = p_doc.getElementsByTagName("signed-credential")[0] + p_cred = p_signed_cred.getElementsByTagName("credential")[0] + p_signatures = p_signed_cred.getElementsByTagName("signatures")[0] + p_sigs = p_signatures.getElementsByTagName("Signature") + + # Create the XML document + doc = Document() + signed_cred = doc.createElement("signed-credential") + doc.appendChild(signed_cred) + + + # Fill in the bit + cred = doc.createElement("credential") + cred.setAttribute("xml:id", refid) + signed_cred.appendChild(cred) + self.append_sub(doc, cred, "type", "privilege") + self.append_sub(doc, cred, "serial", "8") + self.append_sub(doc, cred, "owner_gid", cur_cred.gidCaller.save_to_string()) + self.append_sub(doc, cred, "owner_urn", cur_cred.gidCaller.get_urn()) + self.append_sub(doc, cred, "target_gid", cur_cred.gidObject.save_to_string()) + self.append_sub(doc, cred, "target_urn", cur_cred.gidObject.get_urn()) + self.append_sub(doc, cred, "uuid", "") + self.append_sub(doc, cred, "expires", self.lifeTime) + priveleges = doc.createElement("privileges") + cred.appendChild(priveleges) + + # Add the parent credential if it exists + if self.parent: + cred.appendChild(doc.createElement("parent").appendChild(p_cred)) + + + # Fill out any priveleges here + + + + signed_cred.appendChild(cred) + + + # Fill in the bit + signatures = doc.createElement("signatures") + + sz_sig = signature_template % (refid,refid) + + sdoc = xml.dom.minidom.parseString(sz_sig) + sig_ele = doc.importNode(sdoc.getElemenetsByTagName("Signature")[0], True) + signatures.appendChild(sig_ele) + + + # Add any parent signatures + if self.parent: + for sig in p_sigs: + signatures.appendChild(sig) + + # Get the finished product + self.xml = doc.toxml() + + +## # Call out to xmlsec1 to sign it +## XMLSEC = '/usr/bin/xmlsec1' + +## filename = "/tmp/cred_%d" % random.random() +## f = open(filename, "w") +## f.write(whole); +## f.close() +## signed = os.popen('/usr/bin/xmlsec1 --sign --node-id "%s" --privkey-pem %s,%s %s' +## % ('ref1', self.issuer_privkey, self.issuer_cert, filename)).readlines() +## os.rm(filename) + +## self.encoded = signed + ## # Retrieve the attributes of the credential from the alt-subject-name field # of the X509 certificate. This is automatically done by the various diff --git a/sfa/trust/credential_legacy.py b/sfa/trust/credential_legacy.py new file mode 100644 index 00000000..c33ed6f3 --- /dev/null +++ b/sfa/trust/credential_legacy.py @@ -0,0 +1,247 @@ +## +# Implements SFA Credentials +# +# Credentials are layered on top of certificates, and are essentially a +# certificate that stores a tuple of parameters. +## + +### $Id: credential.py 17477 2010-03-25 16:49:34Z jkarlin $ +### $URL: svn+ssh://svn.planet-lab.org/svn/sfa/branches/geni-api/sfa/trust/credential.py $ + +import xmlrpclib + +from sfa.trust.certificate import Certificate +from sfa.trust.rights import * +from sfa.trust.gid import * +from sfa.util.faults import * +from sfa.util.sfalogging import * + +## +# Credential is a tuple: +# (GIDCaller, GIDObject, LifeTime, Privileges, Delegate) +# +# These fields are encoded using xmlrpc into the subjectAltName field of the +# x509 certificate. Note: Call encode() once the fields have been filled in +# to perform this encoding. + +class CredentialLegacy(Certificate): + gidCaller = None + gidObject = None + lifeTime = None + privileges = None + delegate = False + + ## + # Create a Credential object + # + # @param create If true, create a blank x509 certificate + # @param subject If subject!=None, create an x509 cert with the subject name + # @param string If string!=None, load the credential from the string + # @param filename If filename!=None, load the credential from the file + + def __init__(self, create=False, subject=None, string=None, filename=None): + Certificate.__init__(self, create, subject, string, filename) + + ## + # set the GID of the caller + # + # @param gid GID object of the caller + + def set_gid_caller(self, gid): + self.gidCaller = gid + # gid origin caller is the caller's gid by default + self.gidOriginCaller = gid + + ## + # get the GID of the object + + def get_gid_caller(self): + if not self.gidCaller: + self.decode() + return self.gidCaller + + ## + # set the GID of the object + # + # @param gid GID object of the object + + def set_gid_object(self, gid): + self.gidObject = gid + + ## + # get the GID of the object + + def get_gid_object(self): + if not self.gidObject: + self.decode() + return self.gidObject + + ## + # set the lifetime of this credential + # + # @param lifetime lifetime of credential + + def set_lifetime(self, lifeTime): + self.lifeTime = lifeTime + + ## + # get the lifetime of the credential + + def get_lifetime(self): + if not self.lifeTime: + self.decode() + return self.lifeTime + + ## + # set the delegate bit + # + # @param delegate boolean (True or False) + + def set_delegate(self, delegate): + self.delegate = delegate + + ## + # get the delegate bit + + def get_delegate(self): + if not self.delegate: + self.decode() + return self.delegate + + ## + # set the privileges + # + # @param privs either a comma-separated list of privileges of a RightList object + + def set_privileges(self, privs): + if isinstance(privs, str): + self.privileges = RightList(string = privs) + else: + self.privileges = privs + + ## + # return the privileges as a RightList object + + def get_privileges(self): + if not self.privileges: + self.decode() + return self.privileges + + ## + # determine whether the credential allows a particular operation to be + # performed + # + # @param op_name string specifying name of operation ("lookup", "update", etc) + + def can_perform(self, op_name): + rights = self.get_privileges() + if not rights: + return False + return rights.can_perform(op_name) + + ## + # Encode the attributes of the credential into a string and store that + # string in the alt-subject-name field of the X509 object. This should be + # done immediately before signing the credential. + + def encode(self): + dict = {"gidCaller": None, + "gidObject": None, + "lifeTime": self.lifeTime, + "privileges": None, + "delegate": self.delegate} + if self.gidCaller: + dict["gidCaller"] = self.gidCaller.save_to_string(save_parents=True) + if self.gidObject: + dict["gidObject"] = self.gidObject.save_to_string(save_parents=True) + if self.privileges: + dict["privileges"] = self.privileges.save_to_string() + str = xmlrpclib.dumps((dict,), allow_none=True) + self.set_data('URI:http://' + str) + + ## + # Retrieve the attributes of the credential from the alt-subject-name field + # of the X509 certificate. This is automatically done by the various + # get_* methods of this class and should not need to be called explicitly. + + def decode(self): + data = self.get_data().lstrip('URI:http://') + + if data: + dict = xmlrpclib.loads(data)[0][0] + else: + dict = {} + + self.lifeTime = dict.get("lifeTime", None) + self.delegate = dict.get("delegate", None) + + privStr = dict.get("privileges", None) + if privStr: + self.privileges = RightList(string = privStr) + else: + self.privileges = None + + gidCallerStr = dict.get("gidCaller", None) + if gidCallerStr: + self.gidCaller = GID(string=gidCallerStr) + else: + self.gidCaller = None + + gidObjectStr = dict.get("gidObject", None) + if gidObjectStr: + self.gidObject = GID(string=gidObjectStr) + else: + self.gidObject = None + + ## + # Verify that a chain of credentials is valid (see cert.py:verify). In + # addition to the checks for ordinary certificates, verification also + # ensures that the delegate bit was set by each parent in the chain. If + # a delegate bit was not set, then an exception is thrown. + # + # Each credential must be a subset of the rights of the parent. + + def verify_chain(self, trusted_certs = None): + # do the normal certificate verification stuff + Certificate.verify_chain(self, trusted_certs) + + if self.parent: + # make sure the parent delegated rights to the child + if not self.parent.get_delegate(): + raise MissingDelegateBit(self.parent.get_subject()) + + # make sure the rights given to the child are a subset of the + # parents rights + if not self.parent.get_privileges().is_superset(self.get_privileges()): + raise ChildRightsNotSubsetOfParent(self.get_subject() + + " " + self.parent.get_privileges().save_to_string() + + " " + self.get_privileges().save_to_string()) + + return + + ## + # Dump the contents of a credential to stdout in human-readable format + # + # @param dump_parents If true, also dump the parent certificates + + def dump(self, dump_parents=False): + print "CREDENTIAL", self.get_subject() + + print " privs:", self.get_privileges().save_to_string() + + print " gidCaller:" + gidCaller = self.get_gid_caller() + if gidCaller: + gidCaller.dump(8, dump_parents) + + print " gidObject:" + gidObject = self.get_gid_object() + if gidObject: + gidObject.dump(8, dump_parents) + + print " delegate:", self.get_delegate() + + if self.parent and dump_parents: + print "PARENT", + self.parent.dump(dump_parents) + diff --git a/sfa/trust/hierarchy.py b/sfa/trust/hierarchy.py index b3e730f3..d6839318 100644 --- a/sfa/trust/hierarchy.py +++ b/sfa/trust/hierarchy.py @@ -303,11 +303,11 @@ class Hierarchy: if not parent_hrn or hrn == self.config.SFA_INTERFACE_HRN: # if there is no parent hrn, then it must be self-signed. this # is where we terminate the recursion - cred.set_issuer(auth_info.get_pkey_object(), hrn) + cred.set_issuer_keys(auth_info.get_pkey_object(), auth_info.get_gid_object()) else: # we need the parent's private key in order to sign this GID parent_auth_info = self.get_auth_info(parent_hrn) - cred.set_issuer(parent_auth_info.get_pkey_object(), parent_auth_info.hrn) + cred.set_issuer(parent_auth_info.get_pkey_object(), parent_auth_info.get_gid_object()) cred.set_parent(self.get_auth_cred(parent_hrn, kind)) cred.encode() -- 2.47.0