From 4c3f670fadc7a48ad3d7fb30ff1e3048c5c9a35d Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Tue, 8 Jul 2008 23:35:23 +0000 Subject: [PATCH] added classes for gid & credentials --- util/cert.py | 232 +++++++++++++++++++++++++++++++++++++++++++++ util/credential.py | 130 +++++++++++++++++++++++++ util/gid.py | 55 +++++++++++ util/rights.py | 56 +++++++++++ 4 files changed, 473 insertions(+) create mode 100644 util/cert.py create mode 100644 util/credential.py create mode 100644 util/gid.py create mode 100644 util/rights.py diff --git a/util/cert.py b/util/cert.py new file mode 100644 index 00000000..86700e83 --- /dev/null +++ b/util/cert.py @@ -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 index 00000000..1353f8ab --- /dev/null +++ b/util/credential.py @@ -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 index 00000000..510e849e --- /dev/null +++ b/util/gid.py @@ -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 index 00000000..382fb2f1 --- /dev/null +++ b/util/rights.py @@ -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 + + -- 2.43.0