sfaprotocol is renamed into sfaserverproxy, with class SfaServerProxy
[sfa.git] / sfa / client / sfaclientlib.py
1 # Thierry Parmentelat -- INRIA
2 #
3 # a minimal library for writing "lightweight" SFA clients
4 #
5
6 import os,os.path
7
8 import sfa.util.sfalogging
9
10 from sfa.client.sfaserverproxy import SfaServerProxy
11
12 # see optimizing dependencies below
13 from sfa.trust.certificate import Keypair, Certificate
14
15 ########## 
16 # a helper class to implement the bootstrapping of crypto. material
17 # assuming we are starting from scratch on the client side 
18 # what's needed to complete a full slice creation cycle
19 # (**) prerequisites: 
20 #  (*) a local private key 
21 #  (*) the corresp. public key in the registry 
22 # (**) step1: a self-signed certificate
23 #      default filename is <hrn>.sscert
24 # (**) step2: a user credential
25 #      obtained at the registry with GetSelfCredential
26 #      using the self-signed certificate as the SSL cert
27 #      default filename is <hrn>.user.cred
28 # (**) step3: a registry-provided certificate (i.e. a GID)
29 #      obtained at the registry using Resolve
30 #      using the step2 credential as credential
31 #      default filename is <hrn>.user.gid
32 ##########
33 # from that point on, the GID can/should be used as the SSL cert for anything
34 # a new (slice) credential would be needed for slice operations and can be 
35 # obtained at the registry through GetCredential
36
37 # xxx todo should protect against write file permissions
38 # xxx todo review exceptions
39 class SfaClientBootstrap:
40
41     # xxx todo should account for verbose and timeout that the proxy offers
42     def __init__ (self, user_hrn, registry_url, dir=None, logger=None):
43         self.hrn=user_hrn
44         self.registry_url=registry_url
45         if dir is None: dir="."
46         self.dir=dir
47         if logger is None: 
48             print 'special case for logger'
49             logger = sfa.util.sfalogging.logger
50         self.logger=logger
51
52     #################### public interface
53
54     # xxx should that be called in the constructor ?
55     # typically user_private_key is ~/.ssh/id_rsa
56     def init_private_key_if_missing (self, user_private_key):
57         private_key_filename=self.private_key_filename()
58         if not os.path.isfile (private_key_filename):
59             key=self.plain_read(user_private_key)
60             self.plain_write(private_key_filename, key)
61             os.chmod(private_key_filename,os.stat(user_private_key).st_mode)
62             self.logger.debug("SfaClientBootstrap: Copied private key from %s into %s"%\
63                                   (user_private_key,private_key_filename))
64         
65     # make sure we have the GID at hand
66     def bootstrap_gid (self):
67         if self.any_certificate_filename() is None:
68             self.get_self_signed_cert()
69         self.get_credential()
70         self.get_gid()
71
72     def server_proxy (self, url):
73         return SfaServerProxy (url, self.private_key_filename(), self.gid_filename())
74
75     def get_credential_string (self):
76         return self.plain_read (self.get_credential())
77
78     # more to come to get credentials about other objects (authority/slice)
79
80     #################### private details
81     # stupid stuff
82     def fullpath (self, file): return os.path.join (self.dir,file)
83     # %s -> self.hrn
84     def fullpath_format(self,format): return self.fullpath (format%self.hrn)
85
86     def private_key_filename (self): 
87         return self.fullpath_format ("%s.pkey")
88     def self_signed_cert_filename (self): 
89         return self.fullpath_format ("%s.sscert")
90     def credential_filename (self): 
91         return self.fullpath_format ("%s.user.cred")
92     def gid_filename (self): 
93         return self.fullpath_format ("%s.user.gid")
94
95 # optimizing dependencies
96 # originally we used classes GID or Credential or Certificate 
97 # like e.g. 
98 #        return Credential(filename=self.get_credential()).save_to_string()
99 # but in order to make it simpler to other implementations/languages..
100     def plain_read (self, filename):
101         infile=file(filename,"r")
102         result=infile.read()
103         infile.close()
104         return result
105
106     def plain_write (self, filename, contents):
107         outfile=file(filename,"w")
108         result=outfile.write(contents)
109         outfile.close()
110
111     # the private key
112     def check_private_key (self):
113         if not os.path.isfile (self.private_key_filename()):
114             raise Exception,"No such file %s"%self.private_key_filename()
115         return True
116
117     # get any certificate
118     # rationale  for this method, once we have the gid, it's actually safe
119     # to remove the .sscert
120     def any_certificate_filename (self):
121         attempts=[ self.gid_filename(), self.self_signed_cert_filename() ]
122         for attempt in attempts:
123             if os.path.isfile (attempt): return attempt
124         return None
125
126     ### step1
127     # unconditionnally
128     def create_self_signed_certificate (self,output):
129         self.check_private_key()
130         private_key_filename = self.private_key_filename()
131         keypair=Keypair(filename=private_key_filename)
132         self_signed = Certificate (subject = self.hrn)
133         self_signed.set_pubkey (keypair)
134         self_signed.set_issuer (keypair, self.hrn)
135         self_signed.sign ()
136         self_signed.save_to_file (output)
137         self.logger.debug("SfaClientBootstrap: Created self-signed certificate for %s in %s"%\
138                               (self.hrn,output))
139         return output
140
141     def get_self_signed_cert (self):
142         self_signed_cert_filename = self.self_signed_cert_filename()
143         if os.path.isfile (self_signed_cert_filename):
144             return self_signed_cert_filename
145         return self.create_self_signed_certificate(self_signed_cert_filename)
146         
147     ### step2 
148     # unconditionnally
149     def retrieve_credential (self, output):
150         self.check_private_key()
151         certificate_filename = self.any_certificate_filename()
152         certificate_string = self.plain_read (certificate_filename)
153         registry_proxy = SfaServerProxy (self.registry_url, self.private_key_filename(),
154                                          certificate_filename)
155         credential_string=registry_proxy.GetSelfCredential (certificate_string, self.hrn, "user")
156         self.plain_write (output, credential_string)
157         self.logger.debug("SfaClientBootstrap: Wrote result of GetSelfCredential in %s"%output)
158         return output
159
160     def get_credential (self):
161         credential_filename = self.credential_filename ()
162         if os.path.isfile(credential_filename): 
163             return credential_filename
164         return self.retrieve_credential (credential_filename)
165
166     ### step3
167     # unconditionnally
168     def retrieve_gid (self, hrn, type, output):
169          self.check_private_key()
170          certificate_filename = self.any_certificate_filename()
171          registry_proxy = SfaServerProxy (self.registry_url, self.private_key_filename(),
172                                           certificate_filename)
173          credential_string=self.plain_read (self.get_credential())
174          records = registry_proxy.Resolve (hrn, credential_string)
175          records=[record for record in records if record['type']==type]
176          if not records:
177              # RecordNotFound
178              raise Exception, "hrn %s (%s) unknown to registry %s"%(hrn,type,self.registry_url)
179          record=records[0]
180          self.plain_write (output, record['gid'])
181          self.logger.debug("SfaClientBootstrap: Wrote GID for %s (%s) in %s"% (hrn,type,output))
182          return output
183
184     def get_gid (self):
185         gid_filename=self.gid_filename()
186         if os.path.isfile(gid_filename): 
187             return gid_filename
188         return self.retrieve_gid(self.hrn, "user", gid_filename)
189