added classes for gid & credentials
authorScott Baker <bakers@cs.arizona.edu>
Tue, 8 Jul 2008 23:35:23 +0000 (23:35 +0000)
committerScott Baker <bakers@cs.arizona.edu>
Tue, 8 Jul 2008 23:35:23 +0000 (23:35 +0000)
util/cert.py [new file with mode: 0644]
util/credential.py [new file with mode: 0644]
util/gid.py [new file with mode: 0644]
util/rights.py [new file with mode: 0644]

diff --git a/util/cert.py b/util/cert.py
new file mode 100644 (file)
index 0000000..86700e8
--- /dev/null
@@ -0,0 +1,232 @@
+from OpenSSL import crypto
+import M2Crypto
+from M2Crypto import X509
+from M2Crypto import EVP
+
+class Keypair:
+   key = None       # public/private keypair
+   m2key = None     # public key (m2crypto format)
+
+   def __init__(self, create=False):
+      if create:
+         self.create()
+      pass
+
+   def create(self):
+      self.key = crypto.PKey()
+      self.key.generate_key(crypto.TYPE_RSA, 1024)
+
+   def save_to_file(self, filename):
+      open(filename, 'w').write(self.as_pem())
+
+   def load_from_file(self, filename):
+      buffer = open(filename, 'r').read()
+      self.load_from_string(buffer)
+
+   def load_from_string(self, string):
+      self.key = crypto.load_privatekey(crypto.FILETYPE_PEM, string)
+      self.m2key = M2Crypto.EVP.load_key_string(string)
+
+   def as_pem(self):
+      return crypto.dump_privatekey(crypto.FILETYPE_PEM, self.key)
+
+   def get_m2_pkey(self):
+      if not self.m2key:
+         self.m2key = M2Crypto.EVP.load_key_string(self.as_pem())
+      return self.m2key
+
+   def get_openssl_pkey(self):
+      return self.key
+
+class Certificate:
+   digest = "md5"
+
+   data = None
+   cert = None
+   issuerKey = None
+   issuerSubject = None
+   parent = None
+
+   def __init__(self, create=False, subject=None, string=None, filename=None):
+       if create or subject:
+           self.create()
+       if subject:
+           self.set_subject(subject)
+       if string:
+           self.load_from_string(string)
+       if filename:
+           self.load_from_file(filename)
+
+   def create(self):
+       self.cert = crypto.X509()
+       self.cert.set_serial_number(1)
+       self.cert.gmtime_adj_notBefore(0)
+       self.cert.gmtime_adj_notAfter(60*60*24*365*5) # five years
+
+   def load_from_string(self, string):
+       # if it is a chain of multiple certs, then split off the first one and
+       # load it
+       parts = string.split("-----parent-----", 1)
+       self.cert = crypto.load_certificate(crypto.FILETYPE_PEM, parts[0])
+       
+       # if there are more certs, then create a parent and let the parent load
+       # itself from the remainder of the string
+       if len(parts) > 1:
+           self.parent = Certificate()
+           self.parent.load_from_string(parts[1])
+
+
+   def load_from_file(self, filename):
+       file = open(filename)
+       string = file.read()
+       self.load_from_string(string)
+
+   def save_to_string(self, save_parents=False):
+       string = crypto.dump_certificate(crypto.FILETYPE_PEM, self.cert)
+       if save_parents and self.parent:
+          string = string + "-----parent-----" + self.parent.save_to_string(save_parents)
+       return string
+
+   def save_to_file(self, filename, save_parents=False):
+       string = self.save_to_string(save_parents=save_parents)
+       open(filename, 'w').write(string)
+
+   def set_issuer(self, key, subject=None, cert=None):
+       self.issuerKey = key
+       if subject:
+          # it's a mistake to use subject and cert params at the same time
+          assert(not cert)
+          if isinstance(subject, dict) or isinstance(subject, str):
+             req = crypto.X509Req()
+             reqSubject = req.get_subject()
+             if (isinstance(subject, dict)):
+                for key in reqSubject.keys():
+                    setattr(reqSubject, key, name[key])
+             else:
+                setattr(reqSubject, "CN", subject)
+             subject = reqSubject
+             # subject is not valid once req is out of scope, so save req
+             self.issuerReq = req
+       if cert:
+          # if a cert was supplied, then get the subject from the cert
+          subject = cert.cert.get_issuer()
+       assert(subject)
+       self.issuerSubject = subject
+
+   def get_issuer(self, which="CN"):
+       x = self.cert.get_issuer()
+       return getattr(x, which)
+
+   def set_subject(self, name):
+       req = crypto.X509Req()
+       subj = req.get_subject()
+       if (isinstance(name, dict)):
+           for key in name.keys():
+               setattr(subj, key, name[key])
+       else:
+           setattr(subj, "CN", name)
+       self.cert.set_subject(subj)
+
+   def get_subject(self, which="CN"):
+       x = self.cert.get_subject()
+       return getattr(x, which)
+
+   def set_pubkey(self, key):
+       assert(isinstance(key, Keypair))
+       self.cert.set_pubkey(key.get_openssl_pkey())
+
+   def get_pubkey(self):
+       m2x509 = X509.load_cert_string(self.save_to_string())
+       pkey = Keypair()
+       pkey.key = self.cert.get_pubkey()
+       pkey.m2key = m2x509.get_pubkey()
+       return pkey
+
+   def add_extension(self, name, critical, value):
+       ext = crypto.X509Extension (name, critical, value)
+       self.cert.add_extensions([ext])
+
+   def get_extension(self, name):
+       # pyOpenSSL does not have a way to get certificates
+       m2x509 = X509.load_cert_string(self.save_to_string())
+       value = m2x509.get_ext(name).get_value()
+       return value
+
+   def set_data(self, str):
+       # 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 != None:
+          raise "cannot set subjectAltName more than once"
+       self.data = str
+       self.add_extension("subjectAltName", 0, "URI:http://" + str)
+
+   def get_data(self):
+       if self.data:
+           return self.data
+
+       try:
+           uri = self.get_extension("subjectAltName")
+       except LookupError:
+           self.data = None
+           return self.data
+
+       if not uri.startswith("URI:http://"):
+           raise "bad encoding in subjectAltName"
+       self.data = uri[11:]
+       return self.data
+
+   def sign(self):
+       assert self.cert != None
+       assert self.issuerSubject != None
+       assert self.issuerKey != None
+       self.cert.set_issuer(self.issuerSubject)
+       self.cert.sign(self.issuerKey.get_openssl_pkey(), self.digest)
+
+   def verify(self, pkey):
+       # pyOpenSSL does not have a way to verify signatures
+       m2x509 = X509.load_cert_string(self.save_to_string())
+       m2pkey = pkey.get_m2_pkey()
+       # verify it
+       return m2x509.verify(m2pkey)
+
+       # XXX alternatively, if openssl has been patched, do the much simpler:
+       # try:
+       #   self.cert.verify(pkey.get_openssl_key())
+       #   return 1
+       # except:
+       #   return 0
+
+   def is_signed_by_cert(self, cert):
+       k = cert.get_pubkey()
+       result = self.verify(k)
+       return result
+
+   def set_parent(self, p):
+        self.parent = p
+
+   def get_parent(self):
+        return self.parent
+
+   def verify_chain(self, trusted_certs = None):
+        # if this cert is signed by a trusted_cert, then we are set
+        for trusted_cert in trusted_certs:
+            if self.is_signed_by_cert(trusted_cert):
+                #print self.get_subject(), "is signed by a root"
+                return True
+
+        # if there is no parent, then no way to verify the chain
+        if not self.parent:
+            #print self.get_subject(), "has no parent"
+            return False
+
+        # if it wasn't signed by the parent...
+        if not self.is_signed_by_cert(self.parent):
+            #print self.get_subject(), "is not signed by parent"
+            return False
+
+        # if the parent isn't verified...
+        if not self.parent.verify_chain(trusted_certs):
+            #print self.get_subject(), "parent does not verify"
+            return False
+
+        return True
diff --git a/util/credential.py b/util/credential.py
new file mode 100644 (file)
index 0000000..1353f8a
--- /dev/null
@@ -0,0 +1,130 @@
+from cert import *
+from rights import *
+from gid import *
+import xmlrpclib
+
+# Credential is a tuple:
+#     (GIDCaller, GIDObject, LifeTime, Privileges, Delegate)
+
+class Credential(Certificate):
+    uuid = None
+    hrn = None
+
+    gidCaller = None
+    gidObject = None
+    lifeTime = None
+    privileges = None
+    delegate = False
+
+    def __init__(self, create=False, subject=None, string=None, filename=None):
+        Certificate.__init__(self, create, subject, string, filename)
+
+    def set_gid_caller(self, gid):
+        self.gidCaller = gid
+
+    def get_gid_caller(self):
+        if not self.gidCaller:
+            self.decode()
+        return self.gidCaller
+
+    def set_gid_object(self, gid):
+        self.gidObject = gid
+
+    def get_gid_object(self):
+        if not self.gidObject:
+            self.decode()
+        return self.gidObject
+
+    def set_lifetime(self, lifeTime):
+        self.lifeTime = lifeTime
+
+    def get_lifetime(self):
+        if not self.lifeTime:
+            self.decode()
+        return self.lifeTime
+
+    def set_delegate(self, delegate):
+        self.delegate = delegate
+
+    def get_delegate(self):
+        if not self.delegate:
+            self.decode()
+        return self.delegate
+
+    def set_privileges(self, privs):
+        if isinstance(privs, str):
+            self.privileges = RightList(string = privs)
+        else:
+            self.privileges = privs
+
+    def get_privileges(self):
+        if not self.privileges:
+            self.decode()
+        return self.privileges
+
+    def can_perform(self, op_name):
+        rights = self.get_privileges()
+        if not rights:
+            return False
+        return rights.can_perform(op_name)
+
+    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()
+        if self.gidObject:
+            dict["gidObject"] = self.gidObject.save_to_string()
+        if self.privileges:
+            dict["privileges"] = self.privileges.save_to_string()
+        str = xmlrpclib.dumps((dict,), allow_none=True)
+        self.set_data(str)
+
+    def decode(self):
+        data = self.get_data()
+        if data:
+            dict = xmlrpclib.loads(self.get_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
+
+    def verify_chain(self, trusted_certs = None):
+        # do the normal certificate verification stuff
+        if not Certificate.verify_chain(self, trusted_certs):
+            return False
+
+        if parent:
+            # make sure the parent delegated rights to the child
+            if not parent.delegate:
+                return False
+
+            # XXX todo: make sure child rights are a subset of parent rights
+
+        return True
+
+
+
+
diff --git a/util/gid.py b/util/gid.py
new file mode 100644 (file)
index 0000000..510e849
--- /dev/null
@@ -0,0 +1,55 @@
+from cert import *
+import uuid
+import xmlrpclib
+
+# GID is a tuplie:
+#    (uuid, hrn, public_key)
+
+def create_uuid():
+    return str(uuid.uuid4().int)
+
+class GID(Certificate):
+    uuid = None
+    hrn = None
+
+    def __init__(self, create=False, subject=None, string=None, filename=None, uuid=None, hrn=None):
+        Certificate.__init__(self, create, subject, string, filename)
+        if uuid:
+            self.uuid = uuid
+        if hrn:
+            self.hrn = hrn
+
+    def set_uuid(self, uuid):
+        self.uuid = uuid
+
+    def get_uuid(self):
+        if not self.uuid:
+            self.decode()
+        return self.uuid
+
+    def set_hrn(self, hrn):
+        self.hrn = hrn
+
+    def get_hrn(self):
+        if not self.hrn:
+            self.decode()
+        return self.hrn
+
+    def encode(self):
+        dict = {"uuid": self.uuid,
+                "hrn": self.hrn}
+        str = xmlrpclib.dumps((dict,))
+        self.set_data(str)
+
+    def decode(self):
+        data = self.get_data()
+        if data:
+            dict = xmlrpclib.loads(self.get_data())[0][0]
+        else:
+            dict = {}
+            
+        self.uuid = dict.get("uuid", None)
+        self.hrn = dict.get("hrn", None)
+
+
+
diff --git a/util/rights.py b/util/rights.py
new file mode 100644 (file)
index 0000000..382fb2f
--- /dev/null
@@ -0,0 +1,56 @@
+privilege_table = {"authority": ["*"],
+                   "refresh": ["remove", "update"],
+                   "resolve": ["resolve", "list", "getcredential"],
+                   "sa": ["*"],
+                   "embed": ["getticket", "createslice", "deleteslice", "updateslice"],
+                   "bind": ["getticket", "loanresources"],
+                   "control": ["updateslice", "stopslice", "startslice", "deleteslice"],
+                   "info": ["listslices", "listcomponentresources", "getsliceresources"],
+                   "ma": ["*"]}
+
+class Right:
+   def __init__(self, kind):
+      self.kind = kind
+
+   def can_perform(self, op_name):
+      allowed_ops = privilege_table.get(self.kind.lower(), None)
+      if not allowed_ops:
+         return False
+
+      # if "*" is specified, then all ops are permitted
+      if "*" in allowed_ops:
+         return True
+
+      return (op_name.lower() in allowed_ops)
+
+class RightList:
+    def __init__(self, string=None):
+        self.rights = []
+        if string:
+            self.load_from_string(string)
+
+    def load_from_string(self, string):
+        self.rights = []
+
+        # none == no rights, so leave the list empty
+        if not string:
+            return
+
+        parts = string.split(",")
+        for part in parts:
+            self.rights.append(Right(part))
+
+    def save_to_string(self):
+        right_names = []
+        for right in self.rights:
+            right_names.append(right.kind)
+
+        return ",".join(right_names)
+
+    def can_perform(self, op_name):
+        for right in self.rights:
+            if right.can_perform(op_name):
+                return True
+        return False
+
+