1 #----------------------------------------------------------------------
2 # Copyright (c) 2008 Board of Trustees, Princeton University
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:
11 # The above copyright notice and this permission notice shall be
12 # included in all copies or substantial portions of the Work.
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
22 #----------------------------------------------------------------------
24 # Implements SFA Credentials
26 # Credentials are signed XML files that assign a subject gid privileges to an object gid
30 from types import StringTypes
32 from StringIO import StringIO
33 from tempfile import mkstemp
34 from xml.dom.minidom import Document, parseString
35 from lxml import etree
37 from sfa.util.faults import *
38 from sfa.util.sfalogging import logger
39 from sfa.util.sfatime import utcparse
40 from sfa.trust.certificate import Keypair
41 from sfa.trust.credential_legacy import CredentialLegacy
42 from sfa.trust.rights import Right, Rights
43 from sfa.trust.gid import GID
44 from sfa.util.xrn import urn_to_hrn
47 DEFAULT_CREDENTIAL_LIFETIME = 86400 * 14
51 # . make privs match between PG and PL
52 # . Need to add support for other types of credentials, e.g. tickets
53 # . add namespaces to signed-credential element?
55 signature_template = \
57 <Signature xml:id="Sig_%s" xmlns="http://www.w3.org/2000/09/xmldsig#">
59 <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
60 <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
63 <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
65 <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
66 <DigestValue></DigestValue>
81 # PG formats the template (whitespace) slightly differently.
82 # Note that they don't include the xmlns in the template, but add it later.
83 # Otherwise the two are equivalent.
84 #signature_template_as_in_pg = \
86 #<Signature xml:id="Sig_%s" >
88 # <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
89 # <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
90 # <Reference URI="#%s">
92 # <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
94 # <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
95 # <DigestValue></DigestValue>
102 # <X509IssuerSerial/>
111 # Convert a string into a bool
112 # used to convert an xsd:boolean to a Python boolean
114 if str.lower() in ['true','1']:
120 # Utility function to get the text of an XML element
122 def getTextNode(element, subele):
123 sub = element.getElementsByTagName(subele)[0]
124 if len(sub.childNodes) > 0:
125 return sub.childNodes[0].nodeValue
130 # Utility function to set the text of an XML element
131 # It creates the element, adds the text to it,
132 # and then appends it to the parent.
134 def append_sub(doc, parent, element, text):
135 ele = doc.createElement(element)
136 ele.appendChild(doc.createTextNode(text))
137 parent.appendChild(ele)
140 # Signature contains information about an xmlsec1 signature
141 # for a signed-credential
144 class Signature(object):
146 def __init__(self, string=None):
148 self.issuer_gid = None
165 def set_refid(self, id):
168 def get_issuer_gid(self):
173 def set_issuer_gid(self, gid):
177 doc = parseString(self.xml)
178 sig = doc.getElementsByTagName("Signature")[0]
179 self.set_refid(sig.getAttribute("xml:id").strip("Sig_"))
180 keyinfo = sig.getElementsByTagName("X509Data")[0]
181 szgid = getTextNode(keyinfo, "X509Certificate")
182 szgid = "-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----" % szgid
183 self.set_issuer_gid(GID(string=szgid))
186 self.xml = signature_template % (self.get_refid(), self.get_refid())
190 # A credential provides a caller gid with privileges to an object gid.
191 # A signed credential is signed by the object's authority.
193 # Credentials are encoded in one of two ways. The legacy style places
194 # it in the subjectAltName of an X509 certificate. The new credentials
195 # are placed in signed XML.
198 # In general, a signed credential obtained externally should
199 # not be changed else the signature is no longer valid. So, once
200 # you have loaded an existing signed credential, do not call encode() or sign() on it.
202 def filter_creds_by_caller(creds, caller_hrn):
204 Returns a list of creds who's gid caller matches the
207 if not isinstance(creds, list): creds = [creds]
211 tmp_cred = Credential(string=cred)
212 if tmp_cred.get_gid_caller().get_hrn() == caller_hrn:
213 caller_creds.append(cred)
217 class Credential(object):
220 # Create a Credential object
222 # @param create If true, create a blank x509 certificate
223 # @param subject If subject!=None, create an x509 cert with the subject name
224 # @param string If string!=None, load the credential from the string
225 # @param filename If filename!=None, load the credential from the file
226 # FIXME: create and subject are ignored!
227 def __init__(self, create=False, subject=None, string=None, filename=None):
228 self.gidCaller = None
229 self.gidObject = None
230 self.expiration = None
231 self.privileges = None
232 self.issuer_privkey = None
233 self.issuer_gid = None
234 self.issuer_pubkey = None
236 self.signature = None
241 # Check if this is a legacy credential, translate it if so
242 if string or filename:
246 str = file(filename).read()
248 if str.strip().startswith("-----"):
249 self.legacy = CredentialLegacy(False,string=str)
250 self.translate_legacy(str)
255 # Find an xmlsec1 path
256 self.xmlsec_path = ''
257 paths = ['/usr/bin','/usr/local/bin','/bin','/opt/bin','/opt/local/bin']
259 if os.path.isfile(path + '/' + 'xmlsec1'):
260 self.xmlsec_path = path + '/' + 'xmlsec1'
263 def get_subject(self):
264 if not self.gidObject:
266 return self.gidObject.get_subject()
268 def get_signature(self):
269 if not self.signature:
271 return self.signature
273 def set_signature(self, sig):
278 # Translate a legacy credential into a new one
280 # @param String of the legacy credential
282 def translate_legacy(self, str):
283 legacy = CredentialLegacy(False,string=str)
284 self.gidCaller = legacy.get_gid_caller()
285 self.gidObject = legacy.get_gid_object()
286 lifetime = legacy.get_lifetime()
288 self.set_expiration(datetime.datetime.utcnow() + datetime.timedelta(seconds=DEFAULT_CREDENTIAL_LIFETIME))
290 self.set_expiration(int(lifetime))
291 self.lifeTime = legacy.get_lifetime()
292 self.set_privileges(legacy.get_privileges())
293 self.get_privileges().delegate_all_privileges(legacy.get_delegate())
296 # Need the issuer's private key and name
297 # @param key Keypair object containing the private key of the issuer
298 # @param gid GID of the issuing authority
300 def set_issuer_keys(self, privkey, gid):
301 self.issuer_privkey = privkey
302 self.issuer_gid = gid
306 # Set this credential's parent
307 def set_parent(self, cred):
312 # set the GID of the caller
314 # @param gid GID object of the caller
316 def set_gid_caller(self, gid):
318 # gid origin caller is the caller's gid by default
319 self.gidOriginCaller = gid
322 # get the GID of the object
324 def get_gid_caller(self):
325 if not self.gidCaller:
327 return self.gidCaller
330 # set the GID of the object
332 # @param gid GID object of the object
334 def set_gid_object(self, gid):
338 # get the GID of the object
340 def get_gid_object(self):
341 if not self.gidObject:
343 return self.gidObject
348 # Expiration: an absolute UTC time of expiration (as either an int or string or datetime)
350 def set_expiration(self, expiration):
351 if isinstance(expiration, int):
352 self.expiration = datetime.datetime.fromtimestamp(expiration)
353 elif isinstance (expiration, datetime.datetime):
354 self.expiration = expiration
355 elif isinstance (expiration, StringTypes):
356 self.expiration = utcparse (expiration)
358 logger.error ("unexpected input type in Credential.set_expiration")
361 # get the lifetime of the credential (always in datetime format)
363 def get_expiration(self):
364 if not self.expiration:
366 # at this point self.expiration is normalized as a datetime - DON'T call utcparse again
367 return self.expiration
371 def get_lifetime(self):
372 return self.get_expiration()
377 # @param privs either a comma-separated list of privileges of a Rights object
379 def set_privileges(self, privs):
380 if isinstance(privs, str):
381 self.privileges = Rights(string = privs)
383 self.privileges = privs
387 # return the privileges as a Rights object
389 def get_privileges(self):
390 if not self.privileges:
392 return self.privileges
395 # determine whether the credential allows a particular operation to be
398 # @param op_name string specifying name of operation ("lookup", "update", etc)
400 def can_perform(self, op_name):
401 rights = self.get_privileges()
406 return rights.can_perform(op_name)
410 # Encode the attributes of the credential into an XML string
411 # This should be done immediately before signing the credential.
413 # In general, a signed credential obtained externally should
414 # not be changed else the signature is no longer valid. So, once
415 # you have loaded an existing signed credential, do not call encode() or sign() on it.
418 # Create the XML document
420 signed_cred = doc.createElement("signed-credential")
422 # PG adds these. It would be nice to be consistent.
423 # But it's kind of odd for PL to use PG schemas that talk
424 # about tickets, and the PG CM policies.
425 # Note the careful addition of attributes from the parent below...
426 # signed_cred.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance")
427 # signed_cred.setAttribute("xsinoNamespaceSchemaLocation", "http://www.protogeni.net/resources/credential/credential.xsd")
428 # signed_cred.setAttribute("xsi:schemaLocation", "http://www.protogeni.net/resources/credential/ext/policy/1 http://www.protogeni.net/resources/credential/ext/policy/1/policy.xsd")
430 doc.appendChild(signed_cred)
432 # Fill in the <credential> bit
433 cred = doc.createElement("credential")
434 cred.setAttribute("xml:id", self.get_refid())
435 signed_cred.appendChild(cred)
436 append_sub(doc, cred, "type", "privilege")
437 append_sub(doc, cred, "serial", "8")
438 append_sub(doc, cred, "owner_gid", self.gidCaller.save_to_string())
439 append_sub(doc, cred, "owner_urn", self.gidCaller.get_urn())
440 append_sub(doc, cred, "target_gid", self.gidObject.save_to_string())
441 append_sub(doc, cred, "target_urn", self.gidObject.get_urn())
442 append_sub(doc, cred, "uuid", "")
443 if not self.expiration:
444 self.set_expiration(datetime.datetime.utcnow() + datetime.timedelta(seconds=DEFAULT_CREDENTIAL_LIFETIME))
445 self.expiration = self.expiration.replace(microsecond=0)
446 append_sub(doc, cred, "expires", self.expiration.isoformat())
447 privileges = doc.createElement("privileges")
448 cred.appendChild(privileges)
451 rights = self.get_privileges()
452 for right in rights.rights:
453 priv = doc.createElement("privilege")
454 append_sub(doc, priv, "name", right.kind)
455 append_sub(doc, priv, "can_delegate", str(right.delegate).lower())
456 privileges.appendChild(priv)
458 # Add the parent credential if it exists
460 sdoc = parseString(self.parent.get_xml())
461 # If the root node is a signed-credential (it should be), then
462 # get all its attributes and attach those to our signed_cred
464 # Specifically, PG adds attributes for namespaces (which is reasonable),
465 # and we need to include those again here or else their signature
466 # no longer matches on the credential.
467 # We expect three of these, but here we copy them all:
468 # signed_cred.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance")
469 # signed_cred.setAttribute("xsinoNamespaceSchemaLocation", "http://www.protogeni.net/resources/credential/credential.xsd")
470 # signed_cred.setAttribute("xsi:schemaLocation", "http://www.protogeni.net/resources/credential/ext/policy/1 http://www.protogeni.net/resources/credential/ext/policy/1/policy.xsd")
471 parentRoot = sdoc.documentElement
472 if parentRoot.tagName == "signed-credential" and parentRoot.hasAttributes():
473 for attrIx in range(0, parentRoot.attributes.length):
474 attr = parentRoot.attributes.item(attrIx)
475 # returns the old attribute of same name that was
477 # Below throws InUse exception if we forgot to clone the attribute first
478 oldAttr = signed_cred.setAttributeNode(attr.cloneNode(True))
479 if oldAttr and oldAttr.value != attr.value:
480 msg = "Delegating cred from owner %s to %s over %s replaced attribute %s value %s with %s" % (self.parent.gidCaller.get_urn(), self.gidCaller.get_urn(), self.gidObject.get_urn(), oldAttr.name, oldAttr.value, attr.value)
482 raise CredentialNotVerifiable("Can't encode new valid delegated credential: %s" % msg)
484 p_cred = doc.importNode(sdoc.getElementsByTagName("credential")[0], True)
485 p = doc.createElement("parent")
486 p.appendChild(p_cred)
488 # done handling parent credential
490 # Create the <signatures> tag
491 signatures = doc.createElement("signatures")
492 signed_cred.appendChild(signatures)
494 # Add any parent signatures
496 for cur_cred in self.get_credential_list()[1:]:
497 sdoc = parseString(cur_cred.get_signature().get_xml())
498 ele = doc.importNode(sdoc.getElementsByTagName("Signature")[0], True)
499 signatures.appendChild(ele)
501 # Get the finished product
502 self.xml = doc.toxml()
505 def save_to_random_tmp_file(self):
506 fp, filename = mkstemp(suffix='cred', text=True)
507 fp = os.fdopen(fp, "w")
508 self.save_to_file(filename, save_parents=True, filep=fp)
511 def save_to_file(self, filename, save_parents=True, filep=None):
517 f = open(filename, "w")
521 def save_to_string(self, save_parents=True):
531 def set_refid(self, rid):
535 # Figure out what refids exist, and update this credential's id
536 # so that it doesn't clobber the others. Returns the refids of
539 def updateRefID(self):
541 self.set_refid('ref0')
546 next_cred = self.parent
548 refs.append(next_cred.get_refid())
550 next_cred = next_cred.parent
555 # Find a unique refid for this credential
556 rid = self.get_refid()
559 rid = "ref%d" % (val + 1)
564 # Return the set of parent credential ref ids
573 # Sign the XML file created by encode()
576 # In general, a signed credential obtained externally should
577 # not be changed else the signature is no longer valid. So, once
578 # you have loaded an existing signed credential, do not call encode() or sign() on it.
581 if not self.issuer_privkey or not self.issuer_gid:
583 doc = parseString(self.get_xml())
584 sigs = doc.getElementsByTagName("signatures")[0]
586 # Create the signature template to be signed
587 signature = Signature()
588 signature.set_refid(self.get_refid())
589 sdoc = parseString(signature.get_xml())
590 sig_ele = doc.importNode(sdoc.getElementsByTagName("Signature")[0], True)
591 sigs.appendChild(sig_ele)
593 self.xml = doc.toxml()
596 # Split the issuer GID into multiple certificates if it's a chain
597 chain = GID(filename=self.issuer_gid)
600 gid_files.append(chain.save_to_random_tmp_file(False))
601 if chain.get_parent():
602 chain = chain.get_parent()
607 # Call out to xmlsec1 to sign it
608 ref = 'Sig_%s' % self.get_refid()
609 filename = self.save_to_random_tmp_file()
610 signed = os.popen('%s --sign --node-id "%s" --privkey-pem %s,%s %s' \
611 % (self.xmlsec_path, ref, self.issuer_privkey, ",".join(gid_files), filename)).read()
614 for gid_file in gid_files:
619 # This is no longer a legacy credential
628 # Retrieve the attributes of the credential from the XML.
629 # This is automatically called by the various get_* methods of
630 # this class and should not need to be called explicitly.
635 doc = parseString(self.xml)
637 signed_cred = doc.getElementsByTagName("signed-credential")
639 # Is this a signed-cred or just a cred?
640 if len(signed_cred) > 0:
641 cred = signed_cred[0].getElementsByTagName("credential")[0]
642 signatures = signed_cred[0].getElementsByTagName("signatures")
643 if len(signatures) > 0:
644 sigs = signatures[0].getElementsByTagName("Signature")
646 cred = doc.getElementsByTagName("credential")[0]
649 self.set_refid(cred.getAttribute("xml:id"))
650 self.set_expiration(utcparse(getTextNode(cred, "expires")))
651 self.gidCaller = GID(string=getTextNode(cred, "owner_gid"))
652 self.gidObject = GID(string=getTextNode(cred, "target_gid"))
656 privs = cred.getElementsByTagName("privileges")[0]
658 for priv in privs.getElementsByTagName("privilege"):
659 kind = getTextNode(priv, "name")
660 deleg = str2bool(getTextNode(priv, "can_delegate"))
662 # Convert * into the default privileges for the credential's type
663 # Each inherits the delegatability from the * above
664 _ , type = urn_to_hrn(self.gidObject.get_urn())
665 rl = rlist.determine_rights(type, self.gidObject.get_urn())
670 rlist.add(Right(kind.strip(), deleg))
671 self.set_privileges(rlist)
675 parent = cred.getElementsByTagName("parent")
677 parent_doc = parent[0].getElementsByTagName("credential")[0]
678 parent_xml = parent_doc.toxml()
679 self.parent = Credential(string=parent_xml)
682 # Assign the signatures to the credentials
684 Sig = Signature(string=sig.toxml())
686 for cur_cred in self.get_credential_list():
687 if cur_cred.get_refid() == Sig.get_refid():
688 cur_cred.set_signature(Sig)
693 # trusted_certs: A list of trusted GID filenames (not GID objects!)
694 # Chaining is not supported within the GIDs by xmlsec1.
696 # trusted_certs_required: Should usually be true. Set False means an
697 # empty list of trusted_certs would still let this method pass.
698 # It just skips xmlsec1 verification et al. Only used by some utils
701 # . All of the signatures are valid and that the issuers trace back
702 # to trusted roots (performed by xmlsec1)
703 # . The XML matches the credential schema
704 # . That the issuer of the credential is the authority in the target's urn
705 # . In the case of a delegated credential, this must be true of the root
706 # . That all of the gids presented in the credential are valid
707 # . The credential is not expired
709 # -- For Delegates (credentials with parents)
710 # . The privileges must be a subset of the parent credentials
711 # . The privileges must have "can_delegate" set for each delegated privilege
712 # . The target gid must be the same between child and parents
713 # . The expiry time on the child must be no later than the parent
714 # . The signer of the child must be the owner of the parent
716 # -- Verify does *NOT*
717 # . ensure that an xmlrpc client's gid matches a credential gid, that
718 # must be done elsewhere
720 # @param trusted_certs: The certificates of trusted CA certificates
721 def verify(self, trusted_certs=None, schema=None, trusted_certs_required=True):
725 # validate against RelaxNG schema
727 if schema and os.path.exists(schema):
728 tree = etree.parse(StringIO(self.xml))
729 schema_doc = etree.parse(schema)
730 xmlschema = etree.XMLSchema(schema_doc)
731 if not xmlschema.validate(tree):
732 error = xmlschema.error_log.last_error
733 message = "%s (line %s)" % (error.message, error.line)
734 raise CredentialNotVerifiable(message)
736 if trusted_certs_required and trusted_certs is None:
739 # trusted_cert_objects = [GID(filename=f) for f in trusted_certs]
740 trusted_cert_objects = []
741 ok_trusted_certs = []
742 # If caller explicitly passed in None that means skip cert chain validation.
743 # Strange and not typical
744 if trusted_certs is not None:
745 for f in trusted_certs:
747 # Failures here include unreadable files
749 trusted_cert_objects.append(GID(filename=f))
750 ok_trusted_certs.append(f)
751 except Exception, exc:
752 logger.error("Failed to load trusted cert from %s: %r", f, exc)
753 trusted_certs = ok_trusted_certs
755 # Use legacy verification if this is a legacy credential
757 self.legacy.verify_chain(trusted_cert_objects)
758 if self.legacy.client_gid:
759 self.legacy.client_gid.verify_chain(trusted_cert_objects)
760 if self.legacy.object_gid:
761 self.legacy.object_gid.verify_chain(trusted_cert_objects)
764 # make sure it is not expired
765 if self.get_expiration() < datetime.datetime.utcnow():
766 raise CredentialNotVerifiable("Credential expired at %s" % self.expiration.isoformat())
768 # Verify the signatures
769 filename = self.save_to_random_tmp_file()
770 if trusted_certs is not None:
771 cert_args = " ".join(['--trusted-pem %s' % x for x in trusted_certs])
773 # If caller explicitly passed in None that means skip cert chain validation.
774 # Strange and not typical
775 if trusted_certs is not None:
776 # Verify the gids of this cred and of its parents
777 for cur_cred in self.get_credential_list():
778 cur_cred.get_gid_object().verify_chain(trusted_cert_objects)
779 cur_cred.get_gid_caller().verify_chain(trusted_cert_objects)
782 refs.append("Sig_%s" % self.get_refid())
784 parentRefs = self.updateRefID()
785 for ref in parentRefs:
786 refs.append("Sig_%s" % ref)
789 # If caller explicitly passed in None that means skip xmlsec1 validation.
790 # Strange and not typical
791 if trusted_certs is None:
794 # print "Doing %s --verify --node-id '%s' %s %s 2>&1" % \
795 # (self.xmlsec_path, ref, cert_args, filename)
796 verified = os.popen('%s --verify --node-id "%s" %s %s 2>&1' \
797 % (self.xmlsec_path, ref, cert_args, filename)).read()
798 if not verified.strip().startswith("OK"):
799 # xmlsec errors have a msg= which is the interesting bit.
800 mstart = verified.find("msg=")
802 if mstart > -1 and len(verified) > 4:
804 mend = verified.find('\\', mstart)
805 msg = verified[mstart:mend]
806 raise CredentialNotVerifiable("xmlsec1 error verifying cred using Signature ID %s: %s %s" % (ref, msg, verified.strip()))
809 # Verify the parents (delegation)
811 self.verify_parent(self.parent)
813 # Make sure the issuer is the target's authority
818 # Creates a list of the credential and its parents, with the root
819 # (original delegated credential) as the last item in the list
820 def get_credential_list(self):
824 list.append(cur_cred)
826 cur_cred = cur_cred.parent
832 # Make sure the credential's target gid was signed by (or is the same) the entity that signed
833 # the original credential or an authority over that namespace.
834 def verify_issuer(self):
835 root_cred = self.get_credential_list()[-1]
836 root_target_gid = root_cred.get_gid_object()
837 root_cred_signer = root_cred.get_signature().get_issuer_gid()
839 if root_target_gid.is_signed_by_cert(root_cred_signer):
840 # cred signer matches target signer, return success
843 root_target_gid_str = root_target_gid.save_to_string()
844 root_cred_signer_str = root_cred_signer.save_to_string()
845 if root_target_gid_str == root_cred_signer_str:
846 # cred signer is target, return success
849 # See if it the signer is an authority over the domain of the target
850 # Maybe should be (hrn, type) = urn_to_hrn(root_cred_signer.get_urn())
851 root_cred_signer_type = root_cred_signer.get_type()
852 if (root_cred_signer_type == 'authority'):
853 #sfa_logger.debug('Cred signer is an authority')
854 # signer is an authority, see if target is in authority's domain
855 hrn = root_cred_signer.get_hrn()
856 if root_target_gid.get_hrn().startswith(hrn):
859 # We've required that the credential be signed by an authority
860 # for that domain. Reasonable and probably correct.
861 # A looser model would also allow the signer to be an authority
862 # in my control framework - eg My CA or CH. Even if it is not
863 # the CH that issued these, eg, user credentials.
865 # Give up, credential does not pass issuer verification
867 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()))
871 # -- For Delegates (credentials with parents) verify that:
872 # . The privileges must be a subset of the parent credentials
873 # . The privileges must have "can_delegate" set for each delegated privilege
874 # . The target gid must be the same between child and parents
875 # . The expiry time on the child must be no later than the parent
876 # . The signer of the child must be the owner of the parent
877 def verify_parent(self, parent_cred):
878 # make sure the rights given to the child are a subset of the
879 # parents rights (and check delegate bits)
880 if not parent_cred.get_privileges().is_superset(self.get_privileges()):
881 raise ChildRightsNotSubsetOfParent(("Parent cred ref %s rights " % self.parent.get_refid()) +
882 self.parent.get_privileges().save_to_string() + (" not superset of delegated cred ref %s rights " % self.get_refid()) +
883 self.get_privileges().save_to_string())
885 # make sure my target gid is the same as the parent's
886 if not parent_cred.get_gid_object().save_to_string() == \
887 self.get_gid_object().save_to_string():
888 raise CredentialNotVerifiable("Target gid not equal between parent and child")
890 # make sure my expiry time is <= my parent's
891 if not parent_cred.get_expiration() >= self.get_expiration():
892 raise CredentialNotVerifiable("Delegated credential expires after parent")
894 # make sure my signer is the parent's caller
895 if not parent_cred.get_gid_caller().save_to_string(False) == \
896 self.get_signature().get_issuer_gid().save_to_string(False):
897 raise CredentialNotVerifiable("Delegated credential not signed by parent caller")
900 if parent_cred.parent:
901 parent_cred.verify_parent(parent_cred.parent)
904 def delegate(self, delegee_gidfile, caller_keyfile, caller_gidfile):
906 Return a delegated copy of this credential, delegated to the
907 specified gid's user.
909 # get the gid of the object we are delegating
910 object_gid = self.get_gid_object()
911 object_hrn = object_gid.get_hrn()
913 # the hrn of the user who will be delegated to
914 delegee_gid = GID(filename=delegee_gidfile)
915 delegee_hrn = delegee_gid.get_hrn()
917 #user_key = Keypair(filename=keyfile)
918 #user_hrn = self.get_gid_caller().get_hrn()
919 subject_string = "%s delegated to %s" % (object_hrn, delegee_hrn)
920 dcred = Credential(subject=subject_string)
921 dcred.set_gid_caller(delegee_gid)
922 dcred.set_gid_object(object_gid)
923 dcred.set_parent(self)
924 dcred.set_expiration(self.get_expiration())
925 dcred.set_privileges(self.get_privileges())
926 dcred.get_privileges().delegate_all_privileges(True)
927 #dcred.set_issuer_keys(keyfile, delegee_gidfile)
928 dcred.set_issuer_keys(caller_keyfile, caller_gidfile)
935 def get_filename(self):
936 return getattr(self,'filename',None)
939 # Dump the contents of a credential to stdout in human-readable format
941 # @param dump_parents If true, also dump the parent certificates
942 def dump (self, *args, **kwargs):
943 print self.dump_string(*args, **kwargs)
946 def dump_string(self, dump_parents=False):
948 result += "CREDENTIAL %s\n" % self.get_subject()
949 filename=self.get_filename()
950 if filename: result += "Filename %s\n"%filename
951 result += " privs: %s\n" % self.get_privileges().save_to_string()
952 gidCaller = self.get_gid_caller()
954 result += " gidCaller:\n"
955 result += gidCaller.dump_string(8, dump_parents)
957 if self.get_signature():
959 self.get_signature().get_issuer_gid().dump(8, dump_parents)
961 gidObject = self.get_gid_object()
963 result += " gidObject:\n"
964 result += gidObject.dump_string(8, dump_parents)
966 if self.parent and dump_parents:
968 result += self.parent.dump(True)