Lots of credential updates.. can actually perform list now. Delegation/parents/verif...
authorJosh Karlin <jkarlin@bbn.com>
Mon, 5 Apr 2010 21:12:32 +0000 (21:12 +0000)
committerJosh Karlin <jkarlin@bbn.com>
Mon, 5 Apr 2010 21:12:32 +0000 (21:12 +0000)
Makefile
sfa/plc/sfa-import-plc.py
sfa/plc/sfaImport.py
sfa/trust/credential.py
sfa/trust/gid.py
sfa/trust/hierarchy.py
sfa/trust/rights.py
sfa/util/faults.py
sfa/util/method.py
sfa/util/table.py

index 3d3fc9a..65d63e7 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -7,7 +7,7 @@ DESTDIR="/"
 ##########
 all: keyconvert python wsdl
 
-install: keyconvert-install python-install wsdl-install xmlbuilder-install
+install: keyconvert-install python-install wsdl-install xmlbuilder-install 
 
 clean: keyconvert-clean python-clean wsdl-clean
 
index 160ce77..235877c 100755 (executable)
@@ -82,8 +82,9 @@ def main():
     AuthHierarchy = sfaImporter.AuthHierarchy
     TrustedRoots = sfaImporter.TrustedRoots
     table = SfaTable()
+
     if not table.exists():
-        table.create()
+       table.create()
 
     if not level1_auth or level1_auth in ['']:
         level1_auth = None
@@ -155,10 +156,11 @@ def main():
     slices_dict = {}
     for slice in slices:
         slices_dict[slice['slice_id']] = slice
-
     # start importing 
     for site in sites:
         site_hrn = import_auth + "." + site['login_base']
+        print "Importing site: %s" % site_hrn
+
         # import if hrn is not in list of existing hrns or if the hrn exists
         # but its not a site record
         if site_hrn not in existing_hrns or \
index 8bcf420..bf68b6c 100644 (file)
@@ -206,6 +206,7 @@ class sfaImport:
         plc_auth = self.plc_auth
         sitename = site['login_base']
         sitename = cleanup_string(sitename)
+        print 'importing site %s' % sitename
         hrn = parent_hrn + "." + sitename
         urn = hrn_to_urn(hrn, 'authority')
         # Hardcode 'internet2' into the hrn for sites hosting
index 403f772..3b7dda0 100644 (file)
@@ -11,7 +11,7 @@
 import xmlrpclib
 import random
 import os
-
+import datetime
 
 import xml.dom.minidom
 from xml.dom.minidom import Document
@@ -20,14 +20,14 @@ 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 *
+from sfa.util.sfalogging import logger
 
 
 # TODO:
-# . Need to verify credentials
-# . Need to add privileges (make PG and PL privs work together and add delegation per privelege instead of global)
-# . Need to fix lifetime
-# . Need to make sure delegation is fully supported
+# . Need to verify credential parents (delegation)
+# . implement verify_chain
+# . Need to manage ref #s
+# . Need to add privileges (make PG and PL privs work together and add delegation per privilege instead of global)
 # . Need to test
 
 signature_template = \
@@ -58,11 +58,9 @@ signature_template = \
 
 
 
-
-
 ##
 # Credential is a tuple:
-#    (GIDCaller, GIDObject, LifeTime, Privileges, DelegateBit)
+#    (GIDCaller, GIDObject, Expiration (in UTC time), Privileges, DelegateBit)
 #
 # These fields are encoded in one of two ways.  The legacy style places
 # it in the subjectAltName of an X509 certificate.  The new credentials
@@ -72,7 +70,7 @@ signature_template = \
 class Credential(object):
     gidCaller = None
     gidObject = None
-    lifeTime = None
+    expiration = None
     privileges = None
     delegate = False
     issuer_privkey = None
@@ -80,7 +78,7 @@ class Credential(object):
     issuer_pubkey = None
     parent = None
     xml = None
-
+    max_refid = 0
     ##
     # Create a Credential object
     #
@@ -102,9 +100,8 @@ class Credential(object):
                 self.translate_legacy(str)
             else:
                 self.xml = str
-                # Let's not mess around with invalid credentials
-                self.verify_chain()
-
+                self.decode()
+                
     ##
     # Translate a legacy credential into a new one
     #
@@ -114,6 +111,11 @@ class Credential(object):
         legacy = CredentialLegacy(False,string=str)
         self.gidCaller = legacy.get_gid_caller()
         self.gidObject = legacy.get_gid_object()
+        lifetime = legacy.get_lifetime()
+        if not lifetime:
+            self.set_lifetime(3600)
+        else:
+            self.set_lifetime(int(lifetime))
         self.lifeTime = legacy.get_lifetime()
         self.privileges = legacy.get_privileges()
         self.delegate = legacy.get_delegate()
@@ -127,6 +129,12 @@ class Credential(object):
         self.issuer_privkey = privkey
         self.issuer_gid = gid
 
+    def set_issuer(self, issuer):
+        issuer = issuer
+
+    def set_subject(self, subject):
+        subject = subject
+        
     def set_pubkey(self, pubkey):
         self.issuer_pubkey = pubkey
 
@@ -171,17 +179,23 @@ class Credential(object):
     # set the lifetime of this credential
     #
     # @param lifetime lifetime of credential
+    # . if lifeTime is a datetime object, it is used for the expiration time
+    # . if lifeTime is an integer value, it is considered the number of minutes
+    #   remaining before expiration
 
     def set_lifetime(self, lifeTime):
-        self.lifeTime = lifeTime
+        if isinstance(lifeTime, int):
+            self.expiration = datetime.timedelta(seconds=lifeTime*60) + datetime.datetime.utcnow()
+        else:
+            self.expiration = lifeTime
 
     ##
-    # get the lifetime of the credential
+    # get the lifetime of the credential (in minutes)
 
     def get_lifetime(self):
         if not self.lifeTime:
             self.decode()
-        return self.lifeTime
+        return self.expiration
 
     ##
     # set the delegate bit
@@ -226,8 +240,10 @@ class Credential(object):
 
     def can_perform(self, op_name):
         rights = self.get_privileges()
+        
         if not rights:
             return False
+
         return rights.can_perform(op_name)
 
     def append_sub(self, doc, parent, element, text):
@@ -240,10 +256,13 @@ class Credential(object):
     # This should be done immediately before signing the credential.    
 
     def encode(self):
+        p_sigs = None
 
         # Get information from the parent credential
         if self.parent:
-            p_doc = xml.dom.minidom.parseString(self.parent)
+            if not self.parent.xml:
+                self.parent.encode()            
+            p_doc = xml.dom.minidom.parseString(self.parent.xml)
             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]
@@ -256,7 +275,9 @@ class Credential(object):
         
 
         # Fill in the <credential> bit
-        refid = "ref1"
+        refid = "ref%d" % (self.max_refid + 1)
+        self.max_refid += 1
+        
         cred = doc.createElement("credential")
         cred.setAttribute("xml:id", refid)
         signed_cred.appendChild(cred)
@@ -267,21 +288,26 @@ class Credential(object):
         self.append_sub(doc, cred, "target_gid", self.gidObject.save_to_string())
         self.append_sub(doc, cred, "target_urn", self.gidObject.get_urn())
         self.append_sub(doc, cred, "uuid", "")
-        self.append_sub(doc, cred, "expires", str(self.lifeTime))
-        priveleges = doc.createElement("privileges")
-        cred.appendChild(priveleges)
+        if  not self.expiration:
+            self.set_lifetime(3600)
+        self.expiration = self.expiration.replace(microsecond=0)
+        self.append_sub(doc, cred, "expires", self.expiration.isoformat())
+        privileges = doc.createElement("privileges")
+        cred.appendChild(privileges)
 
         if self.privileges:
             rights = self.privileges.save_to_string().split(",")
             for right in rights:
-                priv = doc.createElement("privelege")
-                priv.append_sub(doc, priv, "name", right.strip())
-                priv.append_sub(doc, priv, "can_delegate", str(self.delegate))
-                priveleges.appendChild(priv)
+                priv = doc.createElement("privilege")
+                self.append_sub(doc, priv, "name", right.strip())
+                self.append_sub(doc, priv, "can_delegate", str(self.delegate))
+                privileges.appendChild(priv)
 
         # Add the parent credential if it exists
         if self.parent:
-            cred.appendChild(doc.createElement("parent").appendChild(p_cred))         
+            parent = doc.createElement("parent")
+            parent.appendChild(p_cred)
+            cred.appendChild(parent)
         
 
         signed_cred.appendChild(cred)
@@ -295,6 +321,11 @@ class Credential(object):
         sdoc = xml.dom.minidom.parseString(sz_sig)
         sig_ele = doc.importNode(sdoc.getElementsByTagName("Signature")[0], True)
         signatures.appendChild(sig_ele)
+        
+        # Add any existing signatures from the parent credential
+        if p_sigs:
+            for sig in p_sigs:
+                signatures.appendChild(sig)
 
 
         # Add any parent signatures
@@ -308,9 +339,19 @@ class Credential(object):
         #print doc.toprettyxml()
 
 
+    def save_to_random_tmp_file(self):
+        filename = "/tmp/cred_%d" % random.randint(0,999999999)
+        self.save_to_file(filename)
+        return filename
+    
+    def save_to_file(self, filename, save_parents=True):
+        if not self.xml:
+            self.encode()
+        f = open(filename, "w")
+        f.write(self.xml)
+        f.close()
 
-
-    def save_to_string(self):
+    def save_to_string(self, save_parents=True):
         if not self.xml:
             self.encode()
         return self.xml
@@ -320,14 +361,10 @@ class Credential(object):
             self.encode()
         
         # Call out to xmlsec1 to sign it
-        XMLSEC = '/usr/bin/xmlsec1'
-
-        filename = "/tmp/cred_%d" % random.randint(0,999999999)
-        f = open(filename, "w")
-        f.write(self.xml);
-        f.close()
+        ref = 'Sig_ref%d' % self.max_refid
+        filename = self.save_to_random_tmp_file()
         signed = os.popen('/usr/bin/xmlsec1 --sign --node-id "%s" --privkey-pem %s,%s %s' \
-                 % ('ref1', self.issuer_privkey, self.issuer_gid, filename)).read()
+                 % (ref, self.issuer_privkey, self.issuer_gid, filename)).read()
         os.remove(filename)
 
         self.xml = signed
@@ -342,21 +379,26 @@ class Credential(object):
     # this class and should not need to be called explicitly.
 
     def decode(self):
-        p_doc = xml.dom.minidom.parseString(self.xml)
-        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")
-
-        self.lifeTime = self.getTextNode(p_cred, "expires")
-        self.gidCaller = GID(string=self.getTextNode(p_cred, "owner_gid"))
-        self.gidObject = GID(string=self.getTextNode(p_cred, "target_gid"))
-        privs = p_cred.getElementsByTagName("priveleges")[0]
+        doc = xml.dom.minidom.parseString(self.xml)
+        signed_cred = doc.getElementsByTagName("signed-credential")[0]
+        cred = signed_cred.getElementsByTagName("credential")[0]
+        signatures = signed_cred.getElementsByTagName("signatures")[0]
+        sigs = signatures.getElementsByTagName("Signature")
+
+
+        self.max_refid = int(cred.getAttribute("xml:id")[3:])
+        sz_expires = self.getTextNode(cred, "expires")
+        if sz_expires != '':
+            self.expiration = datetime.datetime.strptime(sz_expires, '%Y-%m-%dT%H:%M:%S')            
+        self.lifeTime = self.getTextNode(cred, "expires")
+        self.gidCaller = GID(string=self.getTextNode(cred, "owner_gid"))
+        self.gidObject = GID(string=self.getTextNode(cred, "target_gid"))
+        privs = cred.getElementsByTagName("privileges")[0]
         sz_privs = ''
         delegates = []
-        for priv in privs.getElementsByTagName("privelege"):
+        for priv in privs.getElementsByTagName("privilege"):
             sz_privs += self.getTextNode(priv, "name")
-            sz_privs += ", "
+            sz_privs += ","
             delegates.append(self.getTextNode(priv, "can_delegate"))
 
         # Can we delegate?
@@ -366,61 +408,34 @@ class Credential(object):
 
         # Make the rights list
         sz_privs.rstrip(", ")
-        self.priveleges = RightList(string=sz_privs)
+        self.privileges = RightList(string=sz_privs)
         self.delegate
-            
-        
-##     ##
-##     # 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 for the initial credential:
-    # 1. That the signature is valid
-    # 2. That the xml signer's certificate matches the object's certificate
-    # 3. That the urns match those in the gids
+    # For a simple credential (no delegation) verify..
+    # . That the signature is valid for the credential's xml:id
+    # . That the signer's pub key matches the pub key of the target (object)
+    # . That the target/owner urns match those in the gids
     #
-    # Verify for the delegated credentials:
-    # 1. That the signature is valid
+    # @param trusted_certs: The certificates of trusted CA certificates
     
-    # 4. 
-    # 3. That the object's certificate stays the s
-    # 2. That the GID of the 
+    def verify(self, trusted_certs):
+        if not self.xml:
+            self.decode()        
 
-    #def verify(self, trusted_certs = None):
-        
+        # Verify the sigatures
+        filename = self.save_to_random_tmp_file()
+        cert_args = " ".join(['--trusted-pem %s' % x for x in trusted_certs])
+
+        ref = "Sig_ref%d" % self.max_refid
+        verified = os.popen('/usr/bin/xmlsec1 --verify --node-id "%s" %s %s 2>&1' \
+                            % (ref, cert_args, filename)).read()
+
+        if not verified.strip().startswith("OK"):
+            raise CredentialNotVerifiable("xmlsec1 error: " + verified)
 
+        os.remove(filename)
 
     ##
     # Verify that a chain of credentials is valid (see cert.py:verify). In
@@ -430,23 +445,26 @@ class Credential(object):
     #
     # 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)
+    def verify_chain(self, trusted_certs):
+        return
 
-        if self.parent:
-            # make sure the parent delegated rights to the child
-            if not self.parent.get_delegate():
-                raise MissingDelegateBit(self.parent.get_subject())
+ ##    def verify_chain(self, trusted_certs = None):
+##         # do the normal certificate verification stuff
+##         Certificate.verify_chain(self, trusted_certs)
 
-            # 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())
+##         if self.parent:
+##             # make sure the parent delegated rights to the child
+##             if not self.parent.get_delegate():
+##                 raise MissingDelegateBit(self.parent.get_subject())
 
-        return
+##             # 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
index d62e43e..da35d17 100644 (file)
@@ -10,6 +10,8 @@ import xmlrpclib
 import uuid
 from sfa.trust.certificate import Certificate
 from sfa.util.namespace import *
+from sfa.util.sfalogging import logger
+
 ##
 # Create a new uuid. Returns the UUID as a string.
 
@@ -57,6 +59,8 @@ class GID(Certificate):
     def __init__(self, create=False, subject=None, string=None, filename=None, uuid=None, hrn=None, urn=None):
         
         Certificate.__init__(self, create, subject, string, filename)
+        if subject:
+            logger.info("subject: %s" % subject)
         if uuid:
             self.uuid = int(uuid)
         if hrn:
@@ -108,6 +112,8 @@ class GID(Certificate):
         str = szURN + ", " + szUUID
         self.set_data(str, 'subjectAltName')
 
+        
+
 
     ##
     # Decode the subject-alt-name field of the X509 certificate into the
index 3f0580f..fad5c54 100644 (file)
@@ -309,11 +309,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_keys(auth_info.get_pkey_object(), auth_info.get_gid_object())
+            cred.set_issuer_keys(auth_info.get_privkey_filename(), auth_info.get_gid_filename())
         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_privkey_filename(), parent_auth_info.get_gid_filename())
+            cred.set_issuer_keys(parent_auth_info.get_privkey_filename(), parent_auth_info.get_gid_filename())
             cred.set_parent(self.get_auth_cred(parent_hrn, kind))
 
         cred.encode()
index 922ace7..16ef185 100644 (file)
@@ -10,6 +10,7 @@
 # allows "listslices", "listcomponentresources", etc.
 ##
 
+
 ##
 # privilege_table is a list of priviliges and what operations are allowed
 # per privilege.
index 8e5ece1..5e9cecc 100644 (file)
@@ -95,7 +95,8 @@ class ExistingRecord(SfaFault):
         SfaFault.__init__(self, 111, faultString, extra)
     def __str__(self):
         return repr(self.value)
-        
+
+    
 class NonexistingCredType(SfaFault):
     def __init__(self, value, extra = None):
         self.value = value
@@ -270,3 +271,10 @@ class AccountNotEnabled(SfaFault):
     def __str__(self):
         return repr(self.value)
 
+class CredentialNotVerifiable(SfaFault):
+    def __init__(self, value, extra = None):
+        self.value = value
+        faultString = "Unable to verify credential: %(value)s, " %locals()
+        SfaFault.__init__(self, 115, faultString, extra)
+    def __str__(self):
+        return repr(self.value)
index 1df1245..5af4129 100644 (file)
@@ -77,6 +77,7 @@ class Method (object):
                 raise SfaInvalidAPIMethod, methodname, self.api.interface 
 
             # legacy code cannot be type-checked, due to the way Method.args() works
+
             if not hasattr(self,"skip_typecheck"):
                 (min_args, max_args, defaults) = self.args()
                                
@@ -87,6 +88,7 @@ class Method (object):
                 for name, value, expected in zip(max_args, args, self.accepts):
                     self.type_check(name, value, expected, args)
 
+            
             result = self.call(*args, **kwds)
             runtime = time.time() - start
 
index bccda20..d376f63 100644 (file)
@@ -28,7 +28,7 @@ class SfaTable(list):
 
         if record_filter:
             records = self.find(record_filter)
-            for record in reocrds:
+            for record in records:
                 self.append(record)             
 
     def exists(self):
@@ -179,8 +179,9 @@ class SfaTable(list):
 
     def drop(self):
         try:
-            self.db.do('DROP TABLE IF EXISTS ' + self.tablename)
+            self.db.do('DROP TABLE IF EXISTS ' + self.tablename)            
         except:
+
             try:
                 self.db.do('DROP TABLE ' + self.tablename)
             except: