really fixed the redundant logging issue this time.
[sfa.git] / sfa / trust / credential.py
1 #----------------------------------------------------------------------
2 # Copyright (c) 2008 Board of Trustees, Princeton University
3 #
4 # Permission is hereby granted, free of charge, to any person obtaining
5 # a copy of this software and/or hardware specification (the "Work") to
6 # deal in the Work without restriction, including without limitation the
7 # rights to use, copy, modify, merge, publish, distribute, sublicense,
8 # and/or sell copies of the Work, and to permit persons to whom the Work
9 # is furnished to do so, subject to the following conditions:
10 #
11 # The above copyright notice and this permission notice shall be
12 # included in all copies or substantial portions of the Work.
13 #
14 # THE WORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
15 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
16 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
17 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 
18 # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
19 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
20 # OUT OF OR IN CONNECTION WITH THE WORK OR THE USE OR OTHER DEALINGS 
21 # IN THE WORK.
22 #----------------------------------------------------------------------
23 ##
24 # Implements SFA Credentials
25 #
26 # Credentials are signed XML files that assign a subject gid privileges to an object gid
27 ##
28
29 import os
30 import datetime
31 from tempfile import mkstemp
32 import dateutil.parser
33 from StringIO import StringIO 
34 from xml.dom.minidom import Document, parseString
35 from lxml import etree
36
37 from sfa.util.faults import *
38 from sfa.util.sfalogging import logger
39 from sfa.trust.certificate import Keypair
40 from sfa.trust.credential_legacy import CredentialLegacy
41 from sfa.trust.rights import Right, Rights
42 from sfa.trust.gid import GID
43 from sfa.util.xrn import urn_to_hrn
44
45 # 2 weeks, in seconds 
46 DEFAULT_CREDENTIAL_LIFETIME = 86400 * 14
47
48
49 # TODO:
50 # . make privs match between PG and PL
51 # . Need to add support for other types of credentials, e.g. tickets
52
53
54 signature_template = \
55 '''
56 <Signature xml:id="Sig_%s" xmlns="http://www.w3.org/2000/09/xmldsig#">
57     <SignedInfo>
58       <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
59       <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
60       <Reference URI="#%s">
61       <Transforms>
62         <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
63       </Transforms>
64       <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
65       <DigestValue></DigestValue>
66       </Reference>
67     </SignedInfo>
68     <SignatureValue />
69       <KeyInfo>
70         <X509Data>
71           <X509SubjectName/>
72           <X509IssuerSerial/>
73           <X509Certificate/>
74         </X509Data>
75       <KeyValue />
76       </KeyInfo>
77     </Signature>
78 '''
79
80 ##
81 # Convert a string into a bool
82
83 def str2bool(str):
84     if str.lower() in ['true','1']:
85         return True
86     return False
87
88
89 ##
90 # Utility function to get the text of an XML element
91
92 def getTextNode(element, subele):
93     sub = element.getElementsByTagName(subele)[0]
94     if len(sub.childNodes) > 0:            
95         return sub.childNodes[0].nodeValue
96     else:
97         return None
98         
99 ##
100 # Utility function to set the text of an XML element
101 # It creates the element, adds the text to it,
102 # and then appends it to the parent.
103
104 def append_sub(doc, parent, element, text):
105     ele = doc.createElement(element)
106     ele.appendChild(doc.createTextNode(text))
107     parent.appendChild(ele)
108
109 ##
110 # Signature contains information about an xmlsec1 signature
111 # for a signed-credential
112 #
113
114 class Signature(object):
115    
116     def __init__(self, string=None):
117         self.refid = None
118         self.issuer_gid = None
119         self.xml = None
120         if string:
121             self.xml = string
122             self.decode()
123
124
125     def get_refid(self):
126         if not self.refid:
127             self.decode()
128         return self.refid
129
130     def get_xml(self):
131         if not self.xml:
132             self.encode()
133         return self.xml
134
135     def set_refid(self, id):
136         self.refid = id
137
138     def get_issuer_gid(self):
139         if not self.gid:
140             self.decode()
141         return self.gid        
142
143     def set_issuer_gid(self, gid):
144         self.gid = gid
145
146     def decode(self):
147         doc = parseString(self.xml)
148         sig = doc.getElementsByTagName("Signature")[0]
149         self.set_refid(sig.getAttribute("xml:id").strip("Sig_"))
150         keyinfo = sig.getElementsByTagName("X509Data")[0]
151         szgid = getTextNode(keyinfo, "X509Certificate")
152         szgid = "-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----" % szgid
153         self.set_issuer_gid(GID(string=szgid))        
154         
155     def encode(self):
156         self.xml = signature_template % (self.get_refid(), self.get_refid())
157
158
159 ##
160 # A credential provides a caller gid with privileges to an object gid.
161 # A signed credential is signed by the object's authority.
162 #
163 # Credentials are encoded in one of two ways.  The legacy style places
164 # it in the subjectAltName of an X509 certificate.  The new credentials
165 # are placed in signed XML.
166 #
167 # WARNING:
168 # In general, a signed credential obtained externally should
169 # not be changed else the signature is no longer valid.  So, once
170 # you have loaded an existing signed credential, do not call encode() or sign() on it.
171
172 def filter_creds_by_caller(creds, caller_hrn):
173         """
174         Returns a list of creds who's gid caller matches the
175         specified caller hrn
176         """
177         if not isinstance(creds, list): creds = [creds]
178         caller_creds = []
179         for cred in creds:
180             try:
181                 tmp_cred = Credential(string=cred)
182                 if tmp_cred.get_gid_caller().get_hrn() == caller_hrn:
183                     caller_creds.append(cred)
184             except: pass
185         return caller_creds
186
187 class Credential(object):
188
189     ##
190     # Create a Credential object
191     #
192     # @param create If true, create a blank x509 certificate
193     # @param subject If subject!=None, create an x509 cert with the subject name
194     # @param string If string!=None, load the credential from the string
195     # @param filename If filename!=None, load the credential from the file
196     # FIXME: create and subject are ignored!
197     def __init__(self, create=False, subject=None, string=None, filename=None):
198         self.gidCaller = None
199         self.gidObject = None
200         self.expiration = None
201         self.privileges = None
202         self.issuer_privkey = None
203         self.issuer_gid = None
204         self.issuer_pubkey = None
205         self.parent = None
206         self.signature = None
207         self.xml = None
208         self.refid = None
209         self.legacy = None
210
211         # Check if this is a legacy credential, translate it if so
212         if string or filename:
213             if string:                
214                 str = string
215             elif filename:
216                 str = file(filename).read()
217                 self.filename=filename
218                 
219             if str.strip().startswith("-----"):
220                 self.legacy = CredentialLegacy(False,string=str)
221                 self.translate_legacy(str)
222             else:
223                 self.xml = str
224                 self.decode()
225
226         # Find an xmlsec1 path
227         self.xmlsec_path = ''
228         paths = ['/usr/bin','/usr/local/bin','/bin','/opt/bin','/opt/local/bin']
229         for path in paths:
230             if os.path.isfile(path + '/' + 'xmlsec1'):
231                 self.xmlsec_path = path + '/' + 'xmlsec1'
232                 break
233
234     def get_subject(self):
235         if not self.gidObject:
236             self.decode()
237         return self.gidObject.get_subject()   
238
239     def get_signature(self):
240         if not self.signature:
241             self.decode()
242         return self.signature
243
244     def set_signature(self, sig):
245         self.signature = sig
246
247         
248     ##
249     # Translate a legacy credential into a new one
250     #
251     # @param String of the legacy credential
252
253     def translate_legacy(self, str):
254         legacy = CredentialLegacy(False,string=str)
255         self.gidCaller = legacy.get_gid_caller()
256         self.gidObject = legacy.get_gid_object()
257         lifetime = legacy.get_lifetime()
258         if not lifetime:
259             self.set_expiration(datetime.datetime.utcnow() + datetime.timedelta(seconds=DEFAULT_CREDENTIAL_LIFETIME))
260         else:
261             self.set_expiration(int(lifetime))
262         self.lifeTime = legacy.get_lifetime()
263         self.set_privileges(legacy.get_privileges())
264         self.get_privileges().delegate_all_privileges(legacy.get_delegate())
265
266     ##
267     # Need the issuer's private key and name
268     # @param key Keypair object containing the private key of the issuer
269     # @param gid GID of the issuing authority
270
271     def set_issuer_keys(self, privkey, gid):
272         self.issuer_privkey = privkey
273         self.issuer_gid = gid
274
275
276     ##
277     # Set this credential's parent
278     def set_parent(self, cred):
279         self.parent = cred
280         self.updateRefID()
281
282     ##
283     # set the GID of the caller
284     #
285     # @param gid GID object of the caller
286
287     def set_gid_caller(self, gid):
288         self.gidCaller = gid
289         # gid origin caller is the caller's gid by default
290         self.gidOriginCaller = gid
291
292     ##
293     # get the GID of the object
294
295     def get_gid_caller(self):
296         if not self.gidCaller:
297             self.decode()
298         return self.gidCaller
299
300     ##
301     # set the GID of the object
302     #
303     # @param gid GID object of the object
304
305     def set_gid_object(self, gid):
306         self.gidObject = gid
307
308     ##
309     # get the GID of the object
310
311     def get_gid_object(self):
312         if not self.gidObject:
313             self.decode()
314         return self.gidObject
315
316
317             
318     ##
319     # Expiration: an absolute UTC time of expiration (as either an int or datetime)
320     # 
321     def set_expiration(self, expiration):
322         if isinstance(expiration, int):
323             self.expiration = datetime.datetime.fromtimestamp(expiration)
324         else:
325             self.expiration = expiration
326             
327
328     ##
329     # get the lifetime of the credential (in datetime format)
330
331     def get_expiration(self):
332         if not self.expiration:
333             self.decode()
334         return self.expiration
335
336     ##
337     # For legacy sake
338     def get_lifetime(self):
339         return self.get_expiration()
340  
341     ##
342     # set the privileges
343     #
344     # @param privs either a comma-separated list of privileges of a Rights object
345
346     def set_privileges(self, privs):
347         if isinstance(privs, str):
348             self.privileges = Rights(string = privs)
349         else:
350             self.privileges = privs
351         
352
353     ##
354     # return the privileges as a Rights object
355
356     def get_privileges(self):
357         if not self.privileges:
358             self.decode()
359         return self.privileges
360
361     ##
362     # determine whether the credential allows a particular operation to be
363     # performed
364     #
365     # @param op_name string specifying name of operation ("lookup", "update", etc)
366
367     def can_perform(self, op_name):
368         rights = self.get_privileges()
369         
370         if not rights:
371             return False
372
373         return rights.can_perform(op_name)
374
375
376     ##
377     # Encode the attributes of the credential into an XML string    
378     # This should be done immediately before signing the credential.    
379     # WARNING:
380     # In general, a signed credential obtained externally should
381     # not be changed else the signature is no longer valid.  So, once
382     # you have loaded an existing signed credential, do not call encode() or sign() on it.
383
384     def encode(self):
385         # Create the XML document
386         doc = Document()
387         signed_cred = doc.createElement("signed-credential")
388         doc.appendChild(signed_cred)  
389         
390         # Fill in the <credential> bit        
391         cred = doc.createElement("credential")
392         cred.setAttribute("xml:id", self.get_refid())
393         signed_cred.appendChild(cred)
394         append_sub(doc, cred, "type", "privilege")
395         append_sub(doc, cred, "serial", "8")
396         append_sub(doc, cred, "owner_gid", self.gidCaller.save_to_string())
397         append_sub(doc, cred, "owner_urn", self.gidCaller.get_urn())
398         append_sub(doc, cred, "target_gid", self.gidObject.save_to_string())
399         append_sub(doc, cred, "target_urn", self.gidObject.get_urn())
400         append_sub(doc, cred, "uuid", "")
401         if not self.expiration:
402             self.set_expiration(datetime.datetime.utcnow() + datetime.timedelta(seconds=DEFAULT_CREDENTIAL_LIFETIME))
403         self.expiration = self.expiration.replace(microsecond=0)
404         append_sub(doc, cred, "expires", self.expiration.isoformat())
405         privileges = doc.createElement("privileges")
406         cred.appendChild(privileges)
407
408         if self.privileges:
409             rights = self.get_privileges()
410             for right in rights.rights:
411                 priv = doc.createElement("privilege")
412                 append_sub(doc, priv, "name", right.kind)
413                 append_sub(doc, priv, "can_delegate", str(right.delegate).lower())
414                 privileges.appendChild(priv)
415
416         # Add the parent credential if it exists
417         if self.parent:
418             sdoc = parseString(self.parent.get_xml())
419             p_cred = doc.importNode(sdoc.getElementsByTagName("credential")[0], True)
420             p = doc.createElement("parent")
421             p.appendChild(p_cred)
422             cred.appendChild(p)
423
424
425         # Create the <signatures> tag
426         signatures = doc.createElement("signatures")
427         signed_cred.appendChild(signatures)
428
429         # Add any parent signatures
430         if self.parent:
431             for cur_cred in self.get_credential_list()[1:]:
432                 sdoc = parseString(cur_cred.get_signature().get_xml())
433                 ele = doc.importNode(sdoc.getElementsByTagName("Signature")[0], True)
434                 signatures.appendChild(ele)
435                 
436         # Get the finished product
437         self.xml = doc.toxml()
438
439
440     def save_to_random_tmp_file(self):       
441         fp, filename = mkstemp(suffix='cred', text=True)
442         fp = os.fdopen(fp, "w")
443         self.save_to_file(filename, save_parents=True, filep=fp)
444         return filename
445     
446     def save_to_file(self, filename, save_parents=True, filep=None):
447         if not self.xml:
448             self.encode()
449         if filep:
450             f = filep 
451         else:
452             f = open(filename, "w")
453         f.write(self.xml)
454         f.close()
455         self.filename=filename
456
457     def save_to_string(self, save_parents=True):
458         if not self.xml:
459             self.encode()
460         return self.xml
461
462     def get_refid(self):
463         if not self.refid:
464             self.refid = 'ref0'
465         return self.refid
466
467     def set_refid(self, rid):
468         self.refid = rid
469
470     ##
471     # Figure out what refids exist, and update this credential's id
472     # so that it doesn't clobber the others.  Returns the refids of
473     # the parents.
474     
475     def updateRefID(self):
476         if not self.parent:
477             self.set_refid('ref0')
478             return []
479         
480         refs = []
481
482         next_cred = self.parent
483         while next_cred:
484             refs.append(next_cred.get_refid())
485             if next_cred.parent:
486                 next_cred = next_cred.parent
487             else:
488                 next_cred = None
489
490         
491         # Find a unique refid for this credential
492         rid = self.get_refid()
493         while rid in refs:
494             val = int(rid[3:])
495             rid = "ref%d" % (val + 1)
496
497         # Set the new refid
498         self.set_refid(rid)
499
500         # Return the set of parent credential ref ids
501         return refs
502
503     def get_xml(self):
504         if not self.xml:
505             self.encode()
506         return self.xml
507
508     ##
509     # Sign the XML file created by encode()
510     #
511     # WARNING:
512     # In general, a signed credential obtained externally should
513     # not be changed else the signature is no longer valid.  So, once
514     # you have loaded an existing signed credential, do not call encode() or sign() on it.
515
516     def sign(self):
517         if not self.issuer_privkey or not self.issuer_gid:
518             return
519         doc = parseString(self.get_xml())
520         sigs = doc.getElementsByTagName("signatures")[0]
521
522         # Create the signature template to be signed
523         signature = Signature()
524         signature.set_refid(self.get_refid())
525         sdoc = parseString(signature.get_xml())        
526         sig_ele = doc.importNode(sdoc.getElementsByTagName("Signature")[0], True)
527         sigs.appendChild(sig_ele)
528
529         self.xml = doc.toxml()
530
531
532         # Split the issuer GID into multiple certificates if it's a chain
533         chain = GID(filename=self.issuer_gid)
534         gid_files = []
535         while chain:
536             gid_files.append(chain.save_to_random_tmp_file(False))
537             if chain.get_parent():
538                 chain = chain.get_parent()
539             else:
540                 chain = None
541
542
543         # Call out to xmlsec1 to sign it
544         ref = 'Sig_%s' % self.get_refid()
545         filename = self.save_to_random_tmp_file()
546         signed = os.popen('%s --sign --node-id "%s" --privkey-pem %s,%s %s' \
547                  % (self.xmlsec_path, ref, self.issuer_privkey, ",".join(gid_files), filename)).read()
548         os.remove(filename)
549
550         for gid_file in gid_files:
551             os.remove(gid_file)
552
553         self.xml = signed
554
555         # This is no longer a legacy credential
556         if self.legacy:
557             self.legacy = None
558
559         # Update signatures
560         self.decode()       
561
562         
563     ##
564     # Retrieve the attributes of the credential from the XML.
565     # This is automatically called by the various get_* methods of
566     # this class and should not need to be called explicitly.
567
568     def decode(self):
569         if not self.xml:
570             return
571         doc = parseString(self.xml)
572         sigs = []
573         signed_cred = doc.getElementsByTagName("signed-credential")
574
575         # Is this a signed-cred or just a cred?
576         if len(signed_cred) > 0:
577             cred = signed_cred[0].getElementsByTagName("credential")[0]
578             signatures = signed_cred[0].getElementsByTagName("signatures")
579             if len(signatures) > 0:
580                 sigs = signatures[0].getElementsByTagName("Signature")
581         else:
582             cred = doc.getElementsByTagName("credential")[0]
583         
584
585         self.set_refid(cred.getAttribute("xml:id"))
586         self.set_expiration(dateutil.parser.parse(getTextNode(cred, "expires")))
587         self.gidCaller = GID(string=getTextNode(cred, "owner_gid"))
588         self.gidObject = GID(string=getTextNode(cred, "target_gid"))   
589
590
591         # Process privileges
592         privs = cred.getElementsByTagName("privileges")[0]
593         rlist = Rights()
594         for priv in privs.getElementsByTagName("privilege"):
595             kind = getTextNode(priv, "name")
596             deleg = str2bool(getTextNode(priv, "can_delegate"))
597             if kind == '*':
598                 # Convert * into the default privileges for the credential's type                
599                 _ , type = urn_to_hrn(self.gidObject.get_urn())
600                 rl = rlist.determine_rights(type, self.gidObject.get_urn())
601                 for r in rl.rights:
602                     rlist.add(r)
603             else:
604                 rlist.add(Right(kind.strip(), deleg))
605         self.set_privileges(rlist)
606
607
608         # Is there a parent?
609         parent = cred.getElementsByTagName("parent")
610         if len(parent) > 0:
611             parent_doc = parent[0].getElementsByTagName("credential")[0]
612             parent_xml = parent_doc.toxml()
613             self.parent = Credential(string=parent_xml)
614             self.updateRefID()
615
616         # Assign the signatures to the credentials
617         for sig in sigs:
618             Sig = Signature(string=sig.toxml())
619
620             for cur_cred in self.get_credential_list():
621                 if cur_cred.get_refid() == Sig.get_refid():
622                     cur_cred.set_signature(Sig)
623                                     
624             
625     ##
626     # Verify
627     #   trusted_certs: A list of trusted GID filenames (not GID objects!) 
628     #                  Chaining is not supported within the GIDs by xmlsec1.
629     #    
630     # Verify that:
631     # . All of the signatures are valid and that the issuers trace back
632     #   to trusted roots (performed by xmlsec1)
633     # . The XML matches the credential schema
634     # . That the issuer of the credential is the authority in the target's urn
635     #    . In the case of a delegated credential, this must be true of the root
636     # . That all of the gids presented in the credential are valid
637     # . The credential is not expired
638     #
639     # -- For Delegates (credentials with parents)
640     # . The privileges must be a subset of the parent credentials
641     # . The privileges must have "can_delegate" set for each delegated privilege
642     # . The target gid must be the same between child and parents
643     # . The expiry time on the child must be no later than the parent
644     # . The signer of the child must be the owner of the parent
645     #
646     # -- Verify does *NOT*
647     # . ensure that an xmlrpc client's gid matches a credential gid, that
648     #   must be done elsewhere
649     #
650     # @param trusted_certs: The certificates of trusted CA certificates
651     # @param schema: The RelaxNG schema to validate the credential against 
652     def verify(self, trusted_certs, schema=None):
653         if not self.xml:
654             self.decode()        
655         
656         # validate against RelaxNG schema
657         if not self.legacy:
658             if schema and os.path.exists(schema):
659                 tree = etree.parse(StringIO(self.xml))
660                 schema_doc = etree.parse(schema)
661                 xmlschema = etree.XMLSchema(schema_doc)
662                 if not xmlschema.validate(tree):
663                     error = xmlschema.error_log.last_error
664                     message = "%s (line %s)" % (error.message, error.line)
665                     raise CredentialNotVerifiable(message) 
666             
667
668 #       trusted_cert_objects = [GID(filename=f) for f in trusted_certs]
669         trusted_cert_objects = []
670         ok_trusted_certs = []
671         for f in trusted_certs:
672             try:
673                 # Failures here include unreadable files
674                 # or non PEM files
675                 trusted_cert_objects.append(GID(filename=f))
676                 ok_trusted_certs.append(f)
677             except Exception, exc:
678                 logger.error("Failed to load trusted cert from %s: %r"%( f, exc))
679         trusted_certs = ok_trusted_certs
680
681         # Use legacy verification if this is a legacy credential
682         if self.legacy:
683             self.legacy.verify_chain(trusted_cert_objects)
684             if self.legacy.client_gid:
685                 self.legacy.client_gid.verify_chain(trusted_cert_objects)
686             if self.legacy.object_gid:
687                 self.legacy.object_gid.verify_chain(trusted_cert_objects)
688             return True
689
690         
691         # make sure it is not expired
692         if self.get_expiration() < datetime.datetime.utcnow():
693             raise CredentialNotVerifiable("Credential expired at %s" % self.expiration.isoformat())
694
695         # Verify the signatures
696         filename = self.save_to_random_tmp_file()
697         cert_args = " ".join(['--trusted-pem %s' % x for x in trusted_certs])
698
699         # Verify the gids of this cred and of its parents
700         for cur_cred in self.get_credential_list():
701             cur_cred.get_gid_object().verify_chain(trusted_cert_objects)
702             cur_cred.get_gid_caller().verify_chain(trusted_cert_objects) 
703
704         refs = []
705         refs.append("Sig_%s" % self.get_refid())
706
707         parentRefs = self.updateRefID()
708         for ref in parentRefs:
709             refs.append("Sig_%s" % ref)
710
711         for ref in refs:
712             verified = os.popen('%s --verify --node-id "%s" %s %s 2>&1' \
713                             % (self.xmlsec_path, ref, cert_args, filename)).read()
714             if not verified.strip().startswith("OK"):
715                 raise CredentialNotVerifiable("xmlsec1 error verifying cert: " + verified)
716         os.remove(filename)
717
718         # Verify the parents (delegation)
719         if self.parent:
720             self.verify_parent(self.parent)
721
722         # Make sure the issuer is the target's authority
723         self.verify_issuer()
724         return True
725
726     ##
727     # Creates a list of the credential and its parents, with the root 
728     # (original delegated credential) as the last item in the list
729     def get_credential_list(self):    
730         cur_cred = self
731         list = []
732         while cur_cred:
733             list.append(cur_cred)
734             if cur_cred.parent:
735                 cur_cred = cur_cred.parent
736             else:
737                 cur_cred = None
738         return list
739     
740     ##
741     # Make sure the credential's target gid was signed by (or is the same) the entity that signed
742     # the original credential or an authority over that namespace.
743     def verify_issuer(self):                
744         root_cred = self.get_credential_list()[-1]
745         root_target_gid = root_cred.get_gid_object()
746         root_cred_signer = root_cred.get_signature().get_issuer_gid()
747
748         if root_target_gid.is_signed_by_cert(root_cred_signer):
749             # cred signer matches target signer, return success
750             return
751
752         root_target_gid_str = root_target_gid.save_to_string()
753         root_cred_signer_str = root_cred_signer.save_to_string()
754         if root_target_gid_str == root_cred_signer_str:
755             # cred signer is target, return success
756             return
757
758         # See if it the signer is an authority over the domain of the target
759         # Maybe should be (hrn, type) = urn_to_hrn(root_cred_signer.get_urn())
760         root_cred_signer_type = root_cred_signer.get_type()
761         if (root_cred_signer_type == 'authority'):
762             #logger.debug('Cred signer is an authority')
763             # signer is an authority, see if target is in authority's domain
764             hrn = root_cred_signer.get_hrn()
765             if root_target_gid.get_hrn().startswith(hrn):
766                 return
767
768         # We've required that the credential be signed by an authority
769         # for that domain. Reasonable and probably correct.
770         # A looser model would also allow the signer to be an authority
771         # in my control framework - eg My CA or CH. Even if it is not
772         # the CH that issued these, eg, user credentials.
773
774         # Give up, credential does not pass issuer verification
775
776         raise CredentialNotVerifiable("Could not verify credential owned by %s for object %s. Cred signer %s not the trusted authority for Cred target %s" % (self.gidCaller.get_urn(), self.gidObject.get_urn(), root_cred_signer.get_hrn(), root_target_gid.get_hrn()))
777
778
779     ##
780     # -- For Delegates (credentials with parents) verify that:
781     # . The privileges must be a subset of the parent credentials
782     # . The privileges must have "can_delegate" set for each delegated privilege
783     # . The target gid must be the same between child and parents
784     # . The expiry time on the child must be no later than the parent
785     # . The signer of the child must be the owner of the parent        
786     def verify_parent(self, parent_cred):
787         # make sure the rights given to the child are a subset of the
788         # parents rights (and check delegate bits)
789         if not parent_cred.get_privileges().is_superset(self.get_privileges()):
790             raise ChildRightsNotSubsetOfParent(
791                 self.parent.get_privileges().save_to_string() + " " +
792                 self.get_privileges().save_to_string())
793
794         # make sure my target gid is the same as the parent's
795         if not parent_cred.get_gid_object().save_to_string() == \
796            self.get_gid_object().save_to_string():
797             raise CredentialNotVerifiable("Target gid not equal between parent and child")
798
799         # make sure my expiry time is <= my parent's
800         if not parent_cred.get_expiration() >= self.get_expiration():
801             raise CredentialNotVerifiable("Delegated credential expires after parent")
802
803         # make sure my signer is the parent's caller
804         if not parent_cred.get_gid_caller().save_to_string(False) == \
805            self.get_signature().get_issuer_gid().save_to_string(False):
806             raise CredentialNotVerifiable("Delegated credential not signed by parent caller")
807                 
808         # Recurse
809         if parent_cred.parent:
810             parent_cred.verify_parent(parent_cred.parent)
811
812
813     def delegate(self, delegee_gidfile, caller_keyfile, caller_gidfile):
814         """
815         Return a delegated copy of this credential, delegated to the 
816         specified gid's user.    
817         """
818         # get the gid of the object we are delegating
819         object_gid = self.get_gid_object()
820         object_hrn = object_gid.get_hrn()        
821  
822         # the hrn of the user who will be delegated to
823         delegee_gid = GID(filename=delegee_gidfile)
824         delegee_hrn = delegee_gid.get_hrn()
825   
826         #user_key = Keypair(filename=keyfile)
827         #user_hrn = self.get_gid_caller().get_hrn()
828         subject_string = "%s delegated to %s" % (object_hrn, delegee_hrn)
829         dcred = Credential(subject=subject_string)
830         dcred.set_gid_caller(delegee_gid)
831         dcred.set_gid_object(object_gid)
832         dcred.set_parent(self)
833         dcred.set_expiration(self.get_expiration())
834         dcred.set_privileges(self.get_privileges())
835         dcred.get_privileges().delegate_all_privileges(True)
836         #dcred.set_issuer_keys(keyfile, delegee_gidfile)
837         dcred.set_issuer_keys(caller_keyfile, caller_gidfile)
838         dcred.encode()
839         dcred.sign()
840
841         return dcred 
842
843     # only informative
844     def get_filename(self):
845         return getattr(self,'filename',None)
846
847     # @param dump_parents If true, also dump the parent certificates
848     def dump (self, *args, **kwargs):
849         print self.dump_string(*args, **kwargs)
850
851     def dump_string(self, dump_parents=False):
852         result=""
853         result += "CREDENTIAL %s\n" % self.get_subject() 
854         filename=self.get_filename()
855         if filename: result += "Filename %s\n"%filename
856         result += "      privs: %s\n" % self.get_privileges().save_to_string()
857         gidCaller = self.get_gid_caller()
858         if gidCaller:
859             result += "  gidCaller:\n"
860             result += gidCaller.dump_string(8, dump_parents)
861
862         if self.get_signature():
863             print "  gidIssuer:"
864             self.get_signature().get_issuer_gid().dump(8, dump_parents)
865
866         gidObject = self.get_gid_object()
867         if gidObject:
868             result += "  gidObject:\n"
869             result += gidObject.dump_string(8, dump_parents)
870
871         if self.parent and dump_parents:
872             result += "\nPARENT"
873             result += self.parent.dump(True)
874
875         return result
876