This is totally busted code, in the midst of adding support for XML credentials....
[sfa.git] / sfa / trust / credential.py
1 ##
2 # Implements SFA Credentials
3 #
4 # Credentials are layered on top of certificates, and are essentially a
5 # certificate that stores a tuple of parameters.
6 ##
7
8 ### $Id$
9 ### $URL$
10
11 import xmlrpclib
12 import random
13 import os
14
15
16 import xml.dom.minidom
17 from xml.dom.minidom import Document
18 from sfa.trust.credential_legacy import CredentialLegacy
19 from sfa.trust.certificate import Certificate
20 from sfa.trust.rights import *
21 from sfa.trust.gid import *
22 from sfa.util.faults import *
23 from sfa.util.sfalogging import *
24
25
26 signed_cred_template = \
27 '''
28 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
29   %s
30 <signed-credential>
31   <signatures>
32   %s
33   </signatures>
34 </signed-credential>
35 '''
36
37
38 credential_template = \
39 '''
40   <credential xml:id="%s">
41     <type>privilege</type>
42     <serial>8</serial>
43     <owner_gid>%s</owner_gid>
44     <owner_urn>%s</owner_urn>
45     <target_gid>%s</target_gid>
46     <target_urn>%s</target_urn>
47     <uuid></uuid>
48     <expires>%s</expires>
49     <privileges>
50     %s
51     </privileges>
52   </credential>
53 '''
54
55
56 signature_template = \
57 '''
58 <Signature xml:id="Sig_%s" xmlns="http://www.w3.org/2000/09/xmldsig#">
59     <SignedInfo>
60       <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
61       <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
62       <Reference URI="#%s">
63       <Transforms>
64         <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
65       </Transforms>
66       <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
67       <DigestValue></DigestValue>
68       </Reference>
69     </SignedInfo>
70     <SignatureValue />
71       <KeyInfo>
72         <X509Data>
73           <X509SubjectName/>
74           <X509IssuerSerial/>
75           <X509Certificate/>
76         </X509Data>
77       <KeyValue />
78       </KeyInfo>
79     </Signature>
80 '''
81
82
83
84
85
86 ##
87 # Credential is a tuple:
88 #    (GIDCaller, GIDObject, LifeTime, Privileges, DelegateBit)
89 #
90 # These fields are encoded in one of two ways.  The legacy style places
91 # it in the subjectAltName of an X509 certificate.  The new credentials
92 # are placed in signed XML.
93
94
95 class Credential(Certificate):
96     gidCaller = None
97     gidObject = None
98     lifeTime = None
99     privileges = None
100     delegate = False
101     issuer_key = None
102     issuer_gid = None
103     issuer_pubkey = None
104     parent = None
105     xml = None
106
107     ##
108     # Create a Credential object
109     #
110     # @param create If true, create a blank x509 certificate
111     # @param subject If subject!=None, create an x509 cert with the subject name
112     # @param string If string!=None, load the credential from the string
113     # @param filename If filename!=None, load the credential from the file
114
115     def __init__(self, create=False, subject=None, string=None, filename=None):
116
117         # Check if this is a legacy credential, translate it if so
118         if string or filename:
119             if str:
120                 str = string
121             elif filename:
122                 str = file(filename).read()
123                 
124             if str.strip().startswith("-----"):
125                 self.translate_legacy(str)
126                 
127
128     ##
129     # Translate a legacy credential into a new one
130     #
131     # @param String of the legacy credential
132
133     def translate_legacy(self, str):
134         legacy = CredentialLegacy(create, subject, string, filename)
135         self.gidCaller = legacy.get_gid_caller()
136         self.gidObject = legacy.get_gid_object()
137         self.lifeTime = legacy.get_lifetime()
138         self.privileges = legacy.get_privileges()
139         self.delegate = legacy.get_delegate()
140         self.encode()
141
142     ##
143     # Need the issuer's private key and name
144     # @param key Keypair object containing the private key of the issuer
145     # @param gid GID of the issuing authority
146
147     def set_issuer_keys(self, privkey, gid):
148         self.issuer_key = privkey
149         self.issuer_gid = gid
150
151     def set_pubkey(self, pubkey):
152         self.issuer_pubkey = pubkey
153
154     def set_parent(self, cred):
155         self.parent = cred
156
157     ##
158     # set the GID of the caller
159     #
160     # @param gid GID object of the caller
161
162     def set_gid_caller(self, gid):
163         self.gidCaller = gid
164         # gid origin caller is the caller's gid by default
165         self.gidOriginCaller = gid
166
167     ##
168     # get the GID of the object
169
170     def get_gid_caller(self):
171         if not self.gidCaller:
172             self.decode()
173         return self.gidCaller
174
175     ##
176     # set the GID of the object
177     #
178     # @param gid GID object of the object
179
180     def set_gid_object(self, gid):
181         self.gidObject = gid
182
183     ##
184     # get the GID of the object
185
186     def get_gid_object(self):
187         if not self.gidObject:
188             self.decode()
189         return self.gidObject
190
191     ##
192     # set the lifetime of this credential
193     #
194     # @param lifetime lifetime of credential
195
196     def set_lifetime(self, lifeTime):
197         self.lifeTime = lifeTime
198
199     ##
200     # get the lifetime of the credential
201
202     def get_lifetime(self):
203         if not self.lifeTime:
204             self.decode()
205         return self.lifeTime
206
207     ##
208     # set the delegate bit
209     #
210     # @param delegate boolean (True or False)
211
212     def set_delegate(self, delegate):
213         self.delegate = delegate
214
215     ##
216     # get the delegate bit
217
218     def get_delegate(self):
219         if not self.delegate:
220             self.decode()
221         return self.delegate
222
223     ##
224     # set the privileges
225     #
226     # @param privs either a comma-separated list of privileges of a RightList object
227
228     def set_privileges(self, privs):
229         if isinstance(privs, str):
230             self.privileges = RightList(string = privs)
231         else:
232             self.privileges = privs
233
234     ##
235     # return the privileges as a RightList object
236
237     def get_privileges(self):
238         if not self.privileges:
239             self.decode()
240         return self.privileges
241
242     ##
243     # determine whether the credential allows a particular operation to be
244     # performed
245     #
246     # @param op_name string specifying name of operation ("lookup", "update", etc)
247
248     def can_perform(self, op_name):
249         rights = self.get_privileges()
250         if not rights:
251             return False
252         return rights.can_perform(op_name)
253
254     def append_sub(self, doc, parent, element, text):
255         parent.appendChild(doc.createElement(element).appendChild(doc.createTextNode(text)))
256
257     ##
258     # Encode the attributes of the credential into an XML string    
259     # This should be done immediately before signing the credential.    
260
261     def encode(self):
262
263         # Get information from the parent credential
264         if self.parent:
265             p_doc = xml.dom.minidom.parseString(self.parent)
266             p_signed_cred = p_doc.getElementsByTagName("signed-credential")[0]
267             p_cred = p_signed_cred.getElementsByTagName("credential")[0]               
268             p_signatures = p_signed_cred.getElementsByTagName("signatures")[0]
269             p_sigs = p_signatures.getElementsByTagName("Signature")
270
271         # Create the XML document
272         doc = Document()
273         signed_cred = doc.createElement("signed-credential")
274         doc.appendChild(signed_cred)  
275         
276
277         # Fill in the <credential> bit
278         cred = doc.createElement("credential")
279         cred.setAttribute("xml:id", refid)
280         signed_cred.appendChild(cred)
281         self.append_sub(doc, cred, "type", "privilege")
282         self.append_sub(doc, cred, "serial", "8")
283         self.append_sub(doc, cred, "owner_gid", cur_cred.gidCaller.save_to_string())
284         self.append_sub(doc, cred, "owner_urn", cur_cred.gidCaller.get_urn())
285         self.append_sub(doc, cred, "target_gid", cur_cred.gidObject.save_to_string())
286         self.append_sub(doc, cred, "target_urn", cur_cred.gidObject.get_urn())
287         self.append_sub(doc, cred, "uuid", "")
288         self.append_sub(doc, cred, "expires", self.lifeTime)
289         priveleges = doc.createElement("privileges")
290         cred.appendChild(priveleges)
291
292         # Add the parent credential if it exists
293         if self.parent:
294             cred.appendChild(doc.createElement("parent").appendChild(p_cred))
295             
296
297         # Fill out any priveleges here
298
299
300
301         signed_cred.appendChild(cred)
302
303
304         # Fill in the <signature> bit
305         signatures = doc.createElement("signatures")
306
307         sz_sig = signature_template % (refid,refid)
308
309         sdoc = xml.dom.minidom.parseString(sz_sig)
310         sig_ele = doc.importNode(sdoc.getElemenetsByTagName("Signature")[0], True)
311         signatures.appendChild(sig_ele)
312
313
314         # Add any parent signatures
315         if self.parent:
316             for sig in p_sigs:
317                 signatures.appendChild(sig)
318
319         # Get the finished product
320         self.xml = doc.toxml()
321
322
323 ##         # Call out to xmlsec1 to sign it
324 ##         XMLSEC = '/usr/bin/xmlsec1'
325
326 ##         filename = "/tmp/cred_%d" % random.random()
327 ##         f = open(filename, "w")
328 ##         f.write(whole);
329 ##         f.close()
330 ##         signed = os.popen('/usr/bin/xmlsec1 --sign --node-id "%s" --privkey-pem %s,%s %s'
331 ##                  % ('ref1', self.issuer_privkey, self.issuer_cert, filename)).readlines()
332 ##         os.rm(filename)
333
334 ##         self.encoded = signed
335         
336     ##
337     # Retrieve the attributes of the credential from the alt-subject-name field
338     # of the X509 certificate. This is automatically done by the various
339     # get_* methods of this class and should not need to be called explicitly.
340
341     def decode(self):
342         data = self.get_data().lstrip('URI:http://')
343         
344         if data:
345             dict = xmlrpclib.loads(data)[0][0]
346         else:
347             dict = {}
348
349         self.lifeTime = dict.get("lifeTime", None)
350         self.delegate = dict.get("delegate", None)
351
352         privStr = dict.get("privileges", None)
353         if privStr:
354             self.privileges = RightList(string = privStr)
355         else:
356             self.privileges = None
357
358         gidCallerStr = dict.get("gidCaller", None)
359         if gidCallerStr:
360             self.gidCaller = GID(string=gidCallerStr)
361         else:
362             self.gidCaller = None
363
364         gidObjectStr = dict.get("gidObject", None)
365         if gidObjectStr:
366             self.gidObject = GID(string=gidObjectStr)
367         else:
368             self.gidObject = None
369
370     ##
371     # Verify that a chain of credentials is valid (see cert.py:verify). In
372     # addition to the checks for ordinary certificates, verification also
373     # ensures that the delegate bit was set by each parent in the chain. If
374     # a delegate bit was not set, then an exception is thrown.
375     #
376     # Each credential must be a subset of the rights of the parent.
377
378     def verify_chain(self, trusted_certs = None):
379         # do the normal certificate verification stuff
380         Certificate.verify_chain(self, trusted_certs)
381
382         if self.parent:
383             # make sure the parent delegated rights to the child
384             if not self.parent.get_delegate():
385                 raise MissingDelegateBit(self.parent.get_subject())
386
387             # make sure the rights given to the child are a subset of the
388             # parents rights
389             if not self.parent.get_privileges().is_superset(self.get_privileges()):
390                 raise ChildRightsNotSubsetOfParent(self.get_subject() 
391                                                    + " " + self.parent.get_privileges().save_to_string()
392                                                    + " " + self.get_privileges().save_to_string())
393
394         return
395
396     ##
397     # Dump the contents of a credential to stdout in human-readable format
398     #
399     # @param dump_parents If true, also dump the parent certificates
400
401     def dump(self, dump_parents=False):
402         print "CREDENTIAL", self.get_subject()
403
404         print "      privs:", self.get_privileges().save_to_string()
405
406         print "  gidCaller:"
407         gidCaller = self.get_gid_caller()
408         if gidCaller:
409             gidCaller.dump(8, dump_parents)
410
411         print "  gidObject:"
412         gidObject = self.get_gid_object()
413         if gidObject:
414             gidObject.dump(8, dump_parents)
415
416         print "   delegate:", self.get_delegate()
417
418         if self.parent and dump_parents:
419            print "PARENT",
420            self.parent.dump(dump_parents)
421