merge from geni_api branch
authorTony Mack <tmack@cs.princeton.edu>
Tue, 13 Jul 2010 18:32:48 +0000 (18:32 +0000)
committerTony Mack <tmack@cs.princeton.edu>
Tue, 13 Jul 2010 18:32:48 +0000 (18:32 +0000)
sfa/trust/certificate.py
sfa/trust/credential.py
sfa/trust/gid.py
sfa/util/api.py

index 12a60e1..a76b22d 100644 (file)
@@ -1,3 +1,26 @@
+#----------------------------------------------------------------------
+# Copyright (c) 2008 Board of Trustees, Princeton University
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and/or hardware specification (the "Work") to
+# deal in the Work without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Work, and to permit persons to whom the Work
+# is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Work.
+#
+# THE WORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
+# OUT OF OR IN CONNECTION WITH THE WORK OR THE USE OR OTHER DEALINGS 
+# IN THE WORK.
+#----------------------------------------------------------------------
+
 ##
 # SFA uses two crypto libraries: pyOpenSSL and M2Crypto to implement
 # the necessary crypto functionality. Ideally just one of these libraries
@@ -28,7 +51,7 @@ from sfa.util.namespace import urn_to_hrn
 from sfa.util.faults import *
 
 def convert_public_key(key):
-    keyconvert_path = "/usr/bin/keyconvert.py"
+    keyconvert_path = "/usr/bin/keyconvert"
     if not os.path.isfile(keyconvert_path):
         raise IOError, "Could not find keyconvert in %s" % keyconvert_path
 
@@ -448,7 +471,7 @@ class Certificate:
         # pyOpenSSL only allows us to add extensions, so if we try to set the
         # same extension more than once, it will not work
         if self.data.has_key(field):
-            raise "cannot set ", field, " more than once"
+            raise "Cannot set ", field, " more than once"
         self.data[field] = str
         self.add_extension(field, 0, str)
 
@@ -552,7 +575,6 @@ class Certificate:
         # Verify a chain of certificates. Each certificate must be signed by
         # the public key contained in it's parent. The chain is recursed
         # until a certificate is found that is signed by a trusted root.
-
         # TODO: verify expiration time
         #print "====Verify Chain====="
         # if this cert is signed by a trusted_cert, then we are set
@@ -563,6 +585,7 @@ class Certificate:
             #print "TRUSTED CERT", trusted_cert.dump()
             #print "Client is signed by Trusted?", self.is_signed_by_cert(trusted_cert)
             if self.is_signed_by_cert(trusted_cert):
+                logger.debug("Cert %s signed by trusted cert %s", self.get_subject(), trusted_cert.get_subject())
                 return trusted_cert
 
         # if there is no parent, then no way to verify the chain
index 37bec48..453401f 100644 (file)
@@ -53,7 +53,6 @@ DEFAULT_CREDENTIAL_LIFETIME = 60 * 60 * 24 * 365 * 2
 # . Need to add support for other types of credentials, e.g. tickets
 
 
-
 signature_template = \
 '''
 <Signature xml:id="Sig_%s" xmlns="http://www.w3.org/2000/09/xmldsig#">
@@ -89,7 +88,6 @@ def str2bool(str):
     return False
 
 
-
 ##
 # Utility function to get the text of an XML element
 
@@ -116,8 +114,7 @@ def append_sub(doc, parent, element, text):
 #
 
 class Signature(object):
-
-    
+   
     def __init__(self, string=None):
         self.refid = None
         self.issuer_gid = None
@@ -127,7 +124,6 @@ class Signature(object):
             self.decode()
 
 
-
     def get_refid(self):
         if not self.refid:
             self.decode()
@@ -175,10 +171,8 @@ class Signature(object):
 # not be changed else the signature is no longer valid.  So, once
 # you have loaded an existing signed credential, do not call encode() or sign() on it.
 
-
 class Credential(object):
 
-
     ##
     # Create a Credential object
     #
@@ -186,7 +180,7 @@ class Credential(object):
     # @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
-
+    # FIXME: create and subject are ignored!
     def __init__(self, create=False, subject=None, string=None, filename=None):
         self.gidCaller = None
         self.gidObject = None
@@ -201,9 +195,6 @@ class Credential(object):
         self.refid = None
         self.legacy = None
 
-
-
-
         # Check if this is a legacy credential, translate it if so
         if string or filename:
             if string:                
@@ -388,7 +379,7 @@ class Credential(object):
         append_sub(doc, cred, "target_gid", self.gidObject.save_to_string())
         append_sub(doc, cred, "target_urn", self.gidObject.get_urn())
         append_sub(doc, cred, "uuid", "")
-        if  not self.expiration:
+        if not self.expiration:
             self.set_lifetime(DEFAULT_CREDENTIAL_LIFETIME)
         self.expiration = self.expiration.replace(microsecond=0)
         append_sub(doc, cred, "expires", self.expiration.isoformat())
@@ -546,9 +537,7 @@ class Credential(object):
             self.legacy = None
 
         # Update signatures
-        self.decode()
-
-        
+        self.decode()       
 
         
     ##
@@ -573,7 +562,6 @@ class Credential(object):
             cred = doc.getElementsByTagName("credential")[0]
         
 
-
         self.set_refid(cred.getAttribute("xml:id"))
         self.set_lifetime(parse(getTextNode(cred, "expires")))
         self.gidCaller = GID(string=getTextNode(cred, "owner_gid"))
@@ -640,11 +628,22 @@ class Credential(object):
     #   must be done elsewhere
     #
     # @param trusted_certs: The certificates of trusted CA certificates
-    
     def verify(self, trusted_certs):
         if not self.xml:
             self.decode()        
-        trusted_cert_objects = [GID(filename=f) for f in trusted_certs]
+
+#        trusted_cert_objects = [GID(filename=f) for f in trusted_certs]
+        trusted_cert_objects = []
+        ok_trusted_certs = []
+        for f in trusted_certs:
+            try:
+                # Failures here include unreadable files
+                # or non PEM files
+                trusted_cert_objects.append(GID(filename=f))
+                ok_trusted_certs.append(f)
+            except Exception, exc:
+                logger.error("Failed to load trusted cert from %s: %r", f, exc)
+        trusted_certs = ok_trusted_certs
 
         # Use legacy verification if this is a legacy credential
         if self.legacy:
@@ -657,20 +656,16 @@ class Credential(object):
         
         # make sure it is not expired
         if self.get_lifetime() < datetime.datetime.utcnow():
-            raise CredentialNotVerifiable("credential is expired")
+            raise CredentialNotVerifiable("Credential expired at %s" % self.expiration.isoformat())
 
         # Verify the signatures
         filename = self.save_to_random_tmp_file()
         cert_args = " ".join(['--trusted-pem %s' % x for x in trusted_certs])
 
         # Verify the gids of this cred and of its parents
-
-
-
         for cur_cred in self.get_credential_list():
             cur_cred.get_gid_object().verify_chain(trusted_cert_objects)
-            cur_cred.get_gid_caller().verify_chain(trusted_cert_objects)            
-
+            cur_cred.get_gid_caller().verify_chain(trusted_cert_objects) 
 
         refs = []
         refs.append("Sig_%s" % self.get_refid())
@@ -683,7 +678,7 @@ class Credential(object):
             verified = os.popen('%s --verify --node-id "%s" %s %s 2>&1' \
                             % (self.xmlsec_path, ref, cert_args, filename)).read()
             if not verified.strip().startswith("OK"):
-                raise CredentialNotVerifiable("xmlsec1 error: " + verified)
+                raise CredentialNotVerifiable("xmlsec1 error verifying cert: " + verified)
         os.remove(filename)
 
         # Verify the parents (delegation)
@@ -730,15 +725,21 @@ class Credential(object):
         # Maybe should be (hrn, type) = urn_to_hrn(root_cred_signer.get_urn())
         root_cred_signer_type = root_cred_signer.get_type()
         if (root_cred_signer_type == 'authority'):
+            #logger.debug('Cred signer is an authority')
             # signer is an authority, see if target is in authority's domain
             hrn = root_cred_signer.get_hrn()
-            domain = hrn[:hrn.rindex('.')]
-            if root_target_gid.get_hrn().startswith(domain):
-                # target is in domain of signer's authority
+            if root_target_gid.get_hrn().startswith(hrn):
                 return
 
+        # We've required that the credential be signed by an authority
+        # for that domain. Reasonable and probably correct.
+        # A looser model would also allow the signer to be an authority
+        # in my control framework - eg My CA or CH. Even if it is not
+        # the CH that issued these, eg, user credentials.
+
         # Give up, credential does not pass issuer verification
-        raise CredentialNotVerifiable("Could not verify credential signer")
+
+        raise CredentialNotVerifiable("Could not verify credential owned by %s for object %s. Cred signer %s not the trusted authority for Cred target %s" % (self.gidCaller.get_urn(), self.gidObject.get_urn(), root_cred_signer.get_hrn(), root_target_gid.get_hrn()))
 
 
     ##
@@ -747,8 +748,7 @@ class Credential(object):
     # . The privileges must have "can_delegate" set for each delegated privilege
     # . The target gid must be the same between child and parents
     # . The expiry time on the child must be no later than the parent
-    # . The signer of the child must be the owner of the parent
-        
+    # . The signer of the child must be the owner of the parent        
     def verify_parent(self, parent_cred):
         # make sure the rights given to the child are a subset of the
         # parents rights (and check delegate bits)
@@ -760,17 +760,18 @@ class Credential(object):
         # make sure my target gid is the same as the parent's
         if not parent_cred.get_gid_object().save_to_string() == \
            self.get_gid_object().save_to_string():
-            raise CredentialNotVerifiable("target gid not equal between parent and child")
+            raise CredentialNotVerifiable("Target gid not equal between parent and child")
 
         # make sure my expiry time is <= my parent's
         if not parent_cred.get_lifetime() >= self.get_lifetime():
-            raise CredentialNotVerifiable("delegated credential expires after parent")
+            raise CredentialNotVerifiable("Delegated credential expires after parent")
 
         # make sure my signer is the parent's caller
         if not parent_cred.get_gid_caller().save_to_string(False) == \
            self.get_signature().get_issuer_gid().save_to_string(False):
-            raise CredentialNotVerifiable("delegated credential not signed by parent caller")
+            raise CredentialNotVerifiable("Delegated credential not signed by parent caller")
                 
+        # Recurse
         if parent_cred.parent:
             parent_cred.verify_parent(parent_cred.parent)
 
@@ -780,7 +781,9 @@ class Credential(object):
     # @param dump_parents If true, also dump the parent certificates
 
     def dump(self, dump_parents=False):
-        print "CREDENTIAL", self.get_subject()
+# FIXME: get_subject doesnt exist
+#        print "CREDENTIAL", self.get_subject()
+        print "CREDENTIAL"
 
         print "      privs:", self.get_privileges().save_to_string()
 
index 9cab1a5..cda25fc 100644 (file)
@@ -27,7 +27,6 @@
 
 ### $Id$
 ### $URL$
-
 import xmlrpclib
 import uuid
 from sfa.trust.certificate import Certificate
@@ -82,7 +81,7 @@ class GID(Certificate):
         
         Certificate.__init__(self, create, subject, string, filename)
         if subject:
-            logger.info("subject: %s" % subject)
+            logger.debug("Creating GID for subject: %s" % subject)
         if uuid:
             self.uuid = int(uuid)
         if hrn:
@@ -204,22 +203,17 @@ class GID(Certificate):
         if self.parent:
             # make sure the parent's hrn is a prefix of the child's hrn
             if not self.get_hrn().startswith(self.parent.get_hrn()):
-                raise GidParentHrn(self.parent.get_subject())
+                #print self.get_hrn(), " ", self.parent.get_hrn()
+                raise GidParentHrn("This cert %s HRN doesnt start with parent HRN %s" % (self.get_hrn(), self.parent.get_hrn()))
         else:
             # make sure that the trusted root's hrn is a prefix of the child's
             trusted_gid = GID(string=trusted_root.save_to_string())
             trusted_type = trusted_gid.get_type()
             trusted_hrn = trusted_gid.get_hrn()
-            if trusted_type == 'authority':
-                # Could add a check for type == 'authority'
-                trusted_hrn = trusted_hrn[:trusted_hrn.rindex('.')]
+            #if trusted_type == 'authority':
+            #    trusted_hrn = trusted_hrn[:trusted_hrn.rindex('.')]
             cur_hrn = self.get_hrn()
             if not self.get_hrn().startswith(trusted_hrn):
-                raise GidParentHrn(trusted_hrn + " " + self.get_hrn())
+                raise GidParentHrn("Trusted roots HRN %s isnt start of this cert %s" % (trusted_hrn, cur_hrn))
 
         return
-
-
-
-
-
index 7452430..5c5813f 100644 (file)
@@ -199,8 +199,6 @@ class BaseAPI:
         # Return result
         response = self.prepare_response(result, method)
         return response
-
-
     
     def prepare_response(self, result, method=""):
         """
@@ -208,7 +206,6 @@ class BaseAPI:
         """   
  
         if self.protocol == 'xmlrpclib':
-            #if not isinstance(result, SfaFault):
             if not isinstance(result, Exception):
                 result = (result,)
             response = xmlrpclib.dumps(result, methodresponse = True, encoding = self.encoding, allow_none = 1)