This is totally busted code, in the midst of adding support for XML credentials....
authorJosh Karlin <jkarlin@bbn.com>
Tue, 30 Mar 2010 21:03:05 +0000 (21:03 +0000)
committerJosh Karlin <jkarlin@bbn.com>
Tue, 30 Mar 2010 21:03:05 +0000 (21:03 +0000)
sfa/client/sfi.py
sfa/managers/registry_manager_pl.py
sfa/plc/api.py
sfa/trust/credential.py
sfa/trust/credential_legacy.py [new file with mode: 0644]
sfa/trust/hierarchy.py

index 0f46a4a..0e3b812 100755 (executable)
@@ -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()
index 7c96927..03462ee 100644 (file)
@@ -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)
index 52f72ff..4cb05b1 100644 (file)
@@ -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)
index 0bdc07d..5b27d2d 100644 (file)
@@ -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 = \
+'''
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+  %s
+<signed-credential>
+  <signatures>
+  %s
+  </signatures>
+</signed-credential>
+'''
+
+
+credential_template = \
+'''
+  <credential xml:id="%s">
+    <type>privilege</type>
+    <serial>8</serial>
+    <owner_gid>%s</owner_gid>
+    <owner_urn>%s</owner_urn>
+    <target_gid>%s</target_gid>
+    <target_urn>%s</target_urn>
+    <uuid></uuid>
+    <expires>%s</expires>
+    <privileges>
+    %s
+    </privileges>
+  </credential>
+'''
+
+
+signature_template = \
+'''
+<Signature xml:id="Sig_%s" xmlns="http://www.w3.org/2000/09/xmldsig#">
+    <SignedInfo>
+      <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
+      <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
+      <Reference URI="#%s">
+      <Transforms>
+        <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
+      </Transforms>
+      <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
+      <DigestValue></DigestValue>
+      </Reference>
+    </SignedInfo>
+    <SignatureValue />
+      <KeyInfo>
+        <X509Data>
+          <X509SubjectName/>
+          <X509IssuerSerial/>
+          <X509Certificate/>
+        </X509Data>
+      <KeyValue />
+      </KeyInfo>
+    </Signature>
+'''
+
+
+
+
+
 ##
 # 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 <credential> 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 <signature> 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 (file)
index 0000000..c33ed6f
--- /dev/null
@@ -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)
+
index b3e730f..d683931 100644 (file)
@@ -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()