X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=sfa%2Fclient%2Fsfaclientlib.py;h=7736e52a45084928a44d2e17baf704086b447692;hb=ecc85e0b923922cf7117d29b380f5284edb88f21;hp=3f6f6bc8d6e624d559ba9bd3aee0f9c8a13dba03;hpb=28eac17c30d5af62b0017e920fd742225e9864be;p=sfa.git diff --git a/sfa/client/sfaclientlib.py b/sfa/client/sfaclientlib.py index 3f6f6bc8..7736e52a 100644 --- a/sfa/client/sfaclientlib.py +++ b/sfa/client/sfaclientlib.py @@ -1,7 +1,9 @@ # Thierry Parmentelat -- INRIA -# -# a minimal library for writing "lightweight" SFA clients -# +""" +a minimal library for writing "lightweight" SFA clients +""" + +from __future__ import print_function # xxx todo # this library should probably check for the expiration date of the various @@ -9,6 +11,9 @@ import sys import os,os.path +import subprocess +from datetime import datetime +from sfa.util.xrn import Xrn import sfa.util.sfalogging # importing sfa.utils.faults does pull a lot of stuff @@ -19,9 +24,10 @@ from sfa.client.sfaserverproxy import SfaServerProxy # see optimizing dependencies below from sfa.trust.certificate import Keypair, Certificate - +from sfa.trust.credential import Credential +from sfa.trust.gid import GID ########## -# a helper class to implement the bootstrapping of crypto. material +# a helper class to implement the bootstrapping of cryptoa. material # assuming we are starting from scratch on the client side # what's needed to complete a full slice creation cycle # (**) prerequisites: @@ -49,7 +55,11 @@ from sfa.trust.certificate import Keypair, Certificate # obtained at the registry with Resolve # using the (step2) user-credential as credential # default filename is ..cred - +# +# (**) additionnally, it might make sense to upgrade a GID file +# into a pkcs12 certificate usable in a browser +# this bundled format allows for embedding the private key +# ########## Implementation notes # @@ -80,11 +90,16 @@ from sfa.trust.certificate import Keypair, Certificate # the usage model is to reuse an existing keypair) # # there might be a more portable, i.e. less language-dependant way, to -# implement this step by exec'ing the openssl command a known -# successful attempt at this approach that worked for Java is -# documented below +# implement this step by exec'ing the openssl command. +# a known successful attempt at this approach that worked +# for Java is documented below # http://nam.ece.upatras.gr/fstoolkit/trac/wiki/JavaSFAClient # +# (*) pkcs12 +# +# the implementation of the pkcs12 wrapping, which is a late addition, +# is done through direct calls to openssl +# #################### class SfaClientException (Exception): pass @@ -108,7 +123,7 @@ class SfaClientBootstrap: ######################################## *_produce methods ### step1 # unconditionnally create a self-signed certificate - def self_signed_cert_produce (self,output): + def self_signed_cert_produce (self, output): self.assert_private_key() private_key_filename = self.private_key_filename() keypair=Keypair(filename=private_key_filename) @@ -118,7 +133,7 @@ class SfaClientBootstrap: self_signed.sign () self_signed.save_to_file (output) self.logger.debug("SfaClientBootstrap: Created self-signed certificate for %s in %s"%\ - (self.hrn,output)) + (self.hrn, output)) return output ### step2 @@ -129,9 +144,15 @@ class SfaClientBootstrap: certificate_filename = self.self_signed_cert_filename() certificate_string = self.plain_read (certificate_filename) self.assert_private_key() - registry_proxy = SfaServerProxy (self.registry_url, self.private_key_filename(), + registry_proxy = SfaServerProxy (self.registry_url, + self.private_key_filename(), certificate_filename) - credential_string=registry_proxy.GetSelfCredential (certificate_string, self.hrn, "user") + try: + credential_string=registry_proxy.GetSelfCredential (certificate_string, self.hrn, "user") + except: + # some urns hrns may replace non hierarchy delimiters '.' with an '_' instead of escaping the '.' + hrn = Xrn(self.hrn).get_hrn().replace('\.', '_') + credential_string=registry_proxy.GetSelfCredential (certificate_string, hrn, "user") self.plain_write (output, credential_string) self.logger.debug("SfaClientBootstrap: Wrote result of GetSelfCredential in %s"%output) return output @@ -179,12 +200,39 @@ class SfaClientBootstrap: records = registry_proxy.Resolve (hrn, credential_string) records=[record for record in records if record['type']==type] if not records: - raise RecordNotFound, "hrn %s (%s) unknown to registry %s"%(hrn,type,self.registry_url) + raise RecordNotFound("hrn %s (%s) unknown to registry %s"%(hrn,type,self.registry_url)) record=records[0] self.plain_write (output, record['gid']) self.logger.debug("SfaClientBootstrap: Wrote GID for %s (%s) in %s"% (hrn,type,output)) return output + +# http://trac.myslice.info/wiki/MySlice/Developer/SFALogin +### produce a pkcs12 bundled certificate from GID and private key +# xxx for now we put a hard-wired password that's just, well, 'password' +# when leaving this empty on the mac, result can't seem to be loaded in keychain.. + def my_pkcs12_produce (self, filename): + password=raw_input("Enter password for p12 certificate: ") + openssl_command=['openssl', 'pkcs12', "-export"] + openssl_command += [ "-password", "pass:%s"%password ] + openssl_command += [ "-inkey", self.private_key_filename()] + openssl_command += [ "-in", self.my_gid_filename()] + openssl_command += [ "-out", filename ] + if subprocess.call(openssl_command) ==0: + print("Successfully created %s"%filename) + else: + print("Failed to create %s"%filename) + + # Returns True if credential file is valid. Otherwise return false. + def validate_credential(self, filename): + valid = True + cred = Credential(filename=filename) + # check if credential is expires + if cred.get_expiration() < datetime.utcnow(): + valid = False + return valid + + #################### public interface # return my_gid, run all missing steps in the bootstrap sequence @@ -226,22 +274,29 @@ class SfaClientBootstrap: # the expected filenames for the various pieces def private_key_filename (self): - return self.fullpath ("%s.pkey"%self.hrn) + return self.fullpath ("%s.pkey" % Xrn.unescape(self.hrn)) def self_signed_cert_filename (self): return self.fullpath ("%s.sscert"%self.hrn) def my_credential_filename (self): return self.credential_filename (self.hrn, "user") + # the tests use sfi -u ; meaning that the slice credential filename + # needs to keep track of the user too def credential_filename (self, hrn, type): - return self.fullpath ("%s.%s.cred"%(hrn,type)) + if type in ['user']: + basename="%s.%s.cred"%(hrn,type) + else: + basename="%s-%s.%s.cred"%(self.hrn,hrn,type) + return self.fullpath (basename) def slice_credential_filename (self, hrn): return self.credential_filename(hrn,'slice') def authority_credential_filename (self, hrn): return self.credential_filename(hrn,'authority') def my_gid_filename (self): - return self.gid_filename ("user", self.hrn) + return self.gid_filename (self.hrn, "user") def gid_filename (self, hrn, type): return self.fullpath ("%s.%s.gid"%(hrn,type)) - + def my_pkcs12_filename (self): + return self.fullpath ("%s.p12"%self.hrn) # optimizing dependencies # originally we used classes GID or Credential or Certificate @@ -261,21 +316,34 @@ class SfaClientBootstrap: def assert_filename (self, filename, kind): if not os.path.isfile (filename): - raise IOError,"Missing %s file %s"%(kind,filename) + raise IOError("Missing %s file %s"%(kind,filename)) return True - def assert_private_key (self): return self.assert_filename (self.private_key_filename(),"private key") - def assert_self_signed_cert (self): return self.assert_filename (self.self_signed_cert_filename(),"self-signed certificate") - def assert_my_credential (self): return self.assert_filename (self.my_credential_filename(),"user's credential") - def assert_my_gid (self): return self.assert_filename (self.my_gid_filename(),"user's GID") + def assert_private_key (self): + return self.assert_filename (self.private_key_filename(),"private key") + def assert_self_signed_cert (self): + return self.assert_filename (self.self_signed_cert_filename(),"self-signed certificate") + def assert_my_credential (self): + return self.assert_filename (self.my_credential_filename(),"user's credential") + def assert_my_gid (self): + return self.assert_filename (self.my_gid_filename(),"user's GID") # decorator to make up the other methods - def get_or_produce (filename_method, produce_method): + def get_or_produce (filename_method, produce_method, validate_method=None): + # default validator returns true def wrap (f): def wrapped (self, *args, **kw): filename=filename_method (self, *args, **kw) - if os.path.isfile ( filename ): return filename + if os.path.isfile ( filename ): + if not validate_method: + return filename + elif validate_method(self, filename): + return filename + else: + # remove invalid file + self.logger.warning ("Removing %s - has expired"%filename) + os.unlink(filename) try: produce_method (self, filename, *args, **kw) return filename @@ -286,26 +354,29 @@ class SfaClientBootstrap: message="Could not produce/retrieve %s (%s -- %s)"%\ (filename,error[0],error[1]) self.logger.log_exc(message) - raise Exception, message + raise Exception(message) return wrapped return wrap @get_or_produce (self_signed_cert_filename, self_signed_cert_produce) def self_signed_cert (self): pass - @get_or_produce (my_credential_filename, my_credential_produce) + @get_or_produce (my_credential_filename, my_credential_produce, validate_credential) def my_credential (self): pass @get_or_produce (my_gid_filename, my_gid_produce) def my_gid (self): pass - @get_or_produce (credential_filename, credential_produce) + @get_or_produce (my_pkcs12_filename, my_pkcs12_produce) + def my_pkcs12 (self): pass + + @get_or_produce (credential_filename, credential_produce, validate_credential) def credential (self, hrn, type): pass - @get_or_produce (slice_credential_filename, slice_credential_produce) + @get_or_produce (slice_credential_filename, slice_credential_produce, validate_credential) def slice_credential (self, hrn): pass - @get_or_produce (authority_credential_filename, authority_credential_produce) + @get_or_produce (authority_credential_filename, authority_credential_produce, validate_credential) def authority_credential (self, hrn): pass @get_or_produce (gid_filename, gid_produce) @@ -327,3 +398,38 @@ class SfaClientBootstrap: def private_key (self): self.assert_private_key() return self.private_key_filename() + + def delegate_credential_string (self, original_credential, to_hrn, to_type='authority'): + """ + sign a delegation credential to someone else + + original_credential : typically one's user- or slice- credential to be delegated to s/b else + to_hrn : the hrn of the person that will be allowed to do stuff on our behalf + to_type : goes with to_hrn, usually 'user' or 'authority' + + returns a string with the delegated credential + + this internally uses self.my_gid() + it also retrieves the gid for to_hrn/to_type + and uses Credential.delegate()""" + + # the gid and hrn of the object we are delegating + if isinstance (original_credential, str): + original_credential = Credential (string=original_credential) + original_gid = original_credential.get_gid_object() + original_hrn = original_gid.get_hrn() + + if not original_credential.get_privileges().get_all_delegate(): + self.logger.error("delegate_credential_string: original credential %s does not have delegate bit set"%original_hrn) + return + + # the delegating user's gid + my_gid = self.my_gid() + + # retrieve the GID for the entity that we're delegating to + to_gidfile = self.gid (to_hrn,to_type) +# to_gid = GID ( to_gidfile ) +# to_hrn = delegee_gid.get_hrn() +# print 'to_hrn',to_hrn + delegated_credential = original_credential.delegate(to_gidfile, self.private_key(), my_gid) + return delegated_credential.save_to_string(save_parents=True)