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, determine_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,float)):
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")
429 signed_cred.setAttribute("xsi:noNamespaceSchemaLocation", "http://www.planet-lab.org/resources/sfa/credential.xsd")
430 signed_cred.setAttribute("xsi:schemaLocation", "http://www.planet-lab.org/resources/sfa/ext/policy/1 http://www.planet-lab.org/resources/sfa/ext/policy.xsd")
432 doc.appendChild(signed_cred)
434 # Fill in the <credential> bit
435 cred = doc.createElement("credential")
436 cred.setAttribute("xml:id", self.get_refid())
437 signed_cred.appendChild(cred)
438 append_sub(doc, cred, "type", "privilege")
439 append_sub(doc, cred, "serial", "8")
440 append_sub(doc, cred, "owner_gid", self.gidCaller.save_to_string())
441 append_sub(doc, cred, "owner_urn", self.gidCaller.get_urn())
442 append_sub(doc, cred, "target_gid", self.gidObject.save_to_string())
443 append_sub(doc, cred, "target_urn", self.gidObject.get_urn())
444 append_sub(doc, cred, "uuid", "")
445 if not self.expiration:
446 self.set_expiration(datetime.datetime.utcnow() + datetime.timedelta(seconds=DEFAULT_CREDENTIAL_LIFETIME))
447 self.expiration = self.expiration.replace(microsecond=0)
448 append_sub(doc, cred, "expires", self.expiration.isoformat())
449 privileges = doc.createElement("privileges")
450 cred.appendChild(privileges)
453 rights = self.get_privileges()
454 for right in rights.rights:
455 priv = doc.createElement("privilege")
456 append_sub(doc, priv, "name", right.kind)
457 append_sub(doc, priv, "can_delegate", str(right.delegate).lower())
458 privileges.appendChild(priv)
460 # Add the parent credential if it exists
462 sdoc = parseString(self.parent.get_xml())
463 # If the root node is a signed-credential (it should be), then
464 # get all its attributes and attach those to our signed_cred
466 # Specifically, PG adds attributes for namespaces (which is reasonable),
467 # and we need to include those again here or else their signature
468 # no longer matches on the credential.
469 # We expect three of these, but here we copy them all:
470 # signed_cred.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance")
471 # signed_cred.setAttribute("xsinoNamespaceSchemaLocation", "http://www.protogeni.net/resources/credential/credential.xsd")
472 # 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")
473 parentRoot = sdoc.documentElement
474 if parentRoot.tagName == "signed-credential" and parentRoot.hasAttributes():
475 for attrIx in range(0, parentRoot.attributes.length):
476 attr = parentRoot.attributes.item(attrIx)
477 # returns the old attribute of same name that was
479 # Below throws InUse exception if we forgot to clone the attribute first
480 oldAttr = signed_cred.setAttributeNode(attr.cloneNode(True))
481 if oldAttr and oldAttr.value != attr.value:
482 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)
484 raise CredentialNotVerifiable("Can't encode new valid delegated credential: %s" % msg)
486 p_cred = doc.importNode(sdoc.getElementsByTagName("credential")[0], True)
487 p = doc.createElement("parent")
488 p.appendChild(p_cred)
490 # done handling parent credential
492 # Create the <signatures> tag
493 signatures = doc.createElement("signatures")
494 signed_cred.appendChild(signatures)
496 # Add any parent signatures
498 for cur_cred in self.get_credential_list()[1:]:
499 sdoc = parseString(cur_cred.get_signature().get_xml())
500 ele = doc.importNode(sdoc.getElementsByTagName("Signature")[0], True)
501 signatures.appendChild(ele)
503 # Get the finished product
504 self.xml = doc.toxml()
507 def save_to_random_tmp_file(self):
508 fp, filename = mkstemp(suffix='cred', text=True)
509 fp = os.fdopen(fp, "w")
510 self.save_to_file(filename, save_parents=True, filep=fp)
513 def save_to_file(self, filename, save_parents=True, filep=None):
519 f = open(filename, "w")
523 def save_to_string(self, save_parents=True):
533 def set_refid(self, rid):
537 # Figure out what refids exist, and update this credential's id
538 # so that it doesn't clobber the others. Returns the refids of
541 def updateRefID(self):
543 self.set_refid('ref0')
548 next_cred = self.parent
550 refs.append(next_cred.get_refid())
552 next_cred = next_cred.parent
557 # Find a unique refid for this credential
558 rid = self.get_refid()
561 rid = "ref%d" % (val + 1)
566 # Return the set of parent credential ref ids
575 # Sign the XML file created by encode()
578 # In general, a signed credential obtained externally should
579 # not be changed else the signature is no longer valid. So, once
580 # you have loaded an existing signed credential, do not call encode() or sign() on it.
583 if not self.issuer_privkey or not self.issuer_gid:
585 doc = parseString(self.get_xml())
586 sigs = doc.getElementsByTagName("signatures")[0]
588 # Create the signature template to be signed
589 signature = Signature()
590 signature.set_refid(self.get_refid())
591 sdoc = parseString(signature.get_xml())
592 sig_ele = doc.importNode(sdoc.getElementsByTagName("Signature")[0], True)
593 sigs.appendChild(sig_ele)
595 self.xml = doc.toxml()
598 # Split the issuer GID into multiple certificates if it's a chain
599 chain = GID(filename=self.issuer_gid)
602 gid_files.append(chain.save_to_random_tmp_file(False))
603 if chain.get_parent():
604 chain = chain.get_parent()
609 # Call out to xmlsec1 to sign it
610 ref = 'Sig_%s' % self.get_refid()
611 filename = self.save_to_random_tmp_file()
612 signed = os.popen('%s --sign --node-id "%s" --privkey-pem %s,%s %s' \
613 % (self.xmlsec_path, ref, self.issuer_privkey, ",".join(gid_files), filename)).read()
616 for gid_file in gid_files:
621 # This is no longer a legacy credential
630 # Retrieve the attributes of the credential from the XML.
631 # This is automatically called by the various get_* methods of
632 # this class and should not need to be called explicitly.
637 doc = parseString(self.xml)
639 signed_cred = doc.getElementsByTagName("signed-credential")
641 # Is this a signed-cred or just a cred?
642 if len(signed_cred) > 0:
643 cred = signed_cred[0].getElementsByTagName("credential")[0]
644 signatures = signed_cred[0].getElementsByTagName("signatures")
645 if len(signatures) > 0:
646 sigs = signatures[0].getElementsByTagName("Signature")
648 cred = doc.getElementsByTagName("credential")[0]
651 self.set_refid(cred.getAttribute("xml:id"))
652 self.set_expiration(utcparse(getTextNode(cred, "expires")))
653 self.gidCaller = GID(string=getTextNode(cred, "owner_gid"))
654 self.gidObject = GID(string=getTextNode(cred, "target_gid"))
658 privs = cred.getElementsByTagName("privileges")[0]
660 for priv in privs.getElementsByTagName("privilege"):
661 kind = getTextNode(priv, "name")
662 deleg = str2bool(getTextNode(priv, "can_delegate"))
664 # Convert * into the default privileges for the credential's type
665 # Each inherits the delegatability from the * above
666 _ , type = urn_to_hrn(self.gidObject.get_urn())
667 rl = determine_rights(type, self.gidObject.get_urn())
672 rlist.add(Right(kind.strip(), deleg))
673 self.set_privileges(rlist)
677 parent = cred.getElementsByTagName("parent")
679 parent_doc = parent[0].getElementsByTagName("credential")[0]
680 parent_xml = parent_doc.toxml()
681 self.parent = Credential(string=parent_xml)
684 # Assign the signatures to the credentials
686 Sig = Signature(string=sig.toxml())
688 for cur_cred in self.get_credential_list():
689 if cur_cred.get_refid() == Sig.get_refid():
690 cur_cred.set_signature(Sig)
695 # trusted_certs: A list of trusted GID filenames (not GID objects!)
696 # Chaining is not supported within the GIDs by xmlsec1.
698 # trusted_certs_required: Should usually be true. Set False means an
699 # empty list of trusted_certs would still let this method pass.
700 # It just skips xmlsec1 verification et al. Only used by some utils
703 # . All of the signatures are valid and that the issuers trace back
704 # to trusted roots (performed by xmlsec1)
705 # . The XML matches the credential schema
706 # . That the issuer of the credential is the authority in the target's urn
707 # . In the case of a delegated credential, this must be true of the root
708 # . That all of the gids presented in the credential are valid
709 # . The credential is not expired
711 # -- For Delegates (credentials with parents)
712 # . The privileges must be a subset of the parent credentials
713 # . The privileges must have "can_delegate" set for each delegated privilege
714 # . The target gid must be the same between child and parents
715 # . The expiry time on the child must be no later than the parent
716 # . The signer of the child must be the owner of the parent
718 # -- Verify does *NOT*
719 # . ensure that an xmlrpc client's gid matches a credential gid, that
720 # must be done elsewhere
722 # @param trusted_certs: The certificates of trusted CA certificates
723 def verify(self, trusted_certs=None, schema=None, trusted_certs_required=True):
727 # validate against RelaxNG schema
729 if schema and os.path.exists(schema):
730 tree = etree.parse(StringIO(self.xml))
731 schema_doc = etree.parse(schema)
732 xmlschema = etree.XMLSchema(schema_doc)
733 if not xmlschema.validate(tree):
734 error = xmlschema.error_log.last_error
735 message = "%s (line %s)" % (error.message, error.line)
736 raise CredentialNotVerifiable(message)
738 if trusted_certs_required and trusted_certs is None:
741 # trusted_cert_objects = [GID(filename=f) for f in trusted_certs]
742 trusted_cert_objects = []
743 ok_trusted_certs = []
744 # If caller explicitly passed in None that means skip cert chain validation.
745 # Strange and not typical
746 if trusted_certs is not None:
747 for f in trusted_certs:
749 # Failures here include unreadable files
751 trusted_cert_objects.append(GID(filename=f))
752 ok_trusted_certs.append(f)
753 except Exception, exc:
754 logger.error("Failed to load trusted cert from %s: %r", f, exc)
755 trusted_certs = ok_trusted_certs
757 # Use legacy verification if this is a legacy credential
759 self.legacy.verify_chain(trusted_cert_objects)
760 if self.legacy.client_gid:
761 self.legacy.client_gid.verify_chain(trusted_cert_objects)
762 if self.legacy.object_gid:
763 self.legacy.object_gid.verify_chain(trusted_cert_objects)
766 # make sure it is not expired
767 if self.get_expiration() < datetime.datetime.utcnow():
768 raise CredentialNotVerifiable("Credential expired at %s" % self.expiration.isoformat())
770 # Verify the signatures
771 filename = self.save_to_random_tmp_file()
772 if trusted_certs is not None:
773 cert_args = " ".join(['--trusted-pem %s' % x for x in trusted_certs])
775 # If caller explicitly passed in None that means skip cert chain validation.
776 # Strange and not typical
777 if trusted_certs is not None:
778 # Verify the gids of this cred and of its parents
779 for cur_cred in self.get_credential_list():
780 cur_cred.get_gid_object().verify_chain(trusted_cert_objects)
781 cur_cred.get_gid_caller().verify_chain(trusted_cert_objects)
784 refs.append("Sig_%s" % self.get_refid())
786 parentRefs = self.updateRefID()
787 for ref in parentRefs:
788 refs.append("Sig_%s" % ref)
791 # If caller explicitly passed in None that means skip xmlsec1 validation.
792 # Strange and not typical
793 if trusted_certs is None:
796 # print "Doing %s --verify --node-id '%s' %s %s 2>&1" % \
797 # (self.xmlsec_path, ref, cert_args, filename)
798 verified = os.popen('%s --verify --node-id "%s" %s %s 2>&1' \
799 % (self.xmlsec_path, ref, cert_args, filename)).read()
800 if not verified.strip().startswith("OK"):
801 # xmlsec errors have a msg= which is the interesting bit.
802 mstart = verified.find("msg=")
804 if mstart > -1 and len(verified) > 4:
806 mend = verified.find('\\', mstart)
807 msg = verified[mstart:mend]
808 raise CredentialNotVerifiable("xmlsec1 error verifying cred using Signature ID %s: %s %s" % (ref, msg, verified.strip()))
811 # Verify the parents (delegation)
813 self.verify_parent(self.parent)
815 # Make sure the issuer is the target's authority
820 # Creates a list of the credential and its parents, with the root
821 # (original delegated credential) as the last item in the list
822 def get_credential_list(self):
826 list.append(cur_cred)
828 cur_cred = cur_cred.parent
834 # Make sure the credential's target gid was signed by (or is the same) the entity that signed
835 # the original credential or an authority over that namespace.
836 def verify_issuer(self):
837 root_cred = self.get_credential_list()[-1]
838 root_target_gid = root_cred.get_gid_object()
839 root_cred_signer = root_cred.get_signature().get_issuer_gid()
841 if root_target_gid.is_signed_by_cert(root_cred_signer):
842 # cred signer matches target signer, return success
845 root_target_gid_str = root_target_gid.save_to_string()
846 root_cred_signer_str = root_cred_signer.save_to_string()
847 if root_target_gid_str == root_cred_signer_str:
848 # cred signer is target, return success
851 # See if it the signer is an authority over the domain of the target
852 # Maybe should be (hrn, type) = urn_to_hrn(root_cred_signer.get_urn())
853 root_cred_signer_type = root_cred_signer.get_type()
854 if (root_cred_signer_type == 'authority'):
855 #sfa_logger.debug('Cred signer is an authority')
856 # signer is an authority, see if target is in authority's domain
857 hrn = root_cred_signer.get_hrn()
858 if root_target_gid.get_hrn().startswith(hrn):
861 # We've required that the credential be signed by an authority
862 # for that domain. Reasonable and probably correct.
863 # A looser model would also allow the signer to be an authority
864 # in my control framework - eg My CA or CH. Even if it is not
865 # the CH that issued these, eg, user credentials.
867 # Give up, credential does not pass issuer verification
869 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()))
873 # -- For Delegates (credentials with parents) verify that:
874 # . The privileges must be a subset of the parent credentials
875 # . The privileges must have "can_delegate" set for each delegated privilege
876 # . The target gid must be the same between child and parents
877 # . The expiry time on the child must be no later than the parent
878 # . The signer of the child must be the owner of the parent
879 def verify_parent(self, parent_cred):
880 # make sure the rights given to the child are a subset of the
881 # parents rights (and check delegate bits)
882 if not parent_cred.get_privileges().is_superset(self.get_privileges()):
883 raise ChildRightsNotSubsetOfParent(("Parent cred ref %s rights " % self.parent.get_refid()) +
884 self.parent.get_privileges().save_to_string() + (" not superset of delegated cred ref %s rights " % self.get_refid()) +
885 self.get_privileges().save_to_string())
887 # make sure my target gid is the same as the parent's
888 if not parent_cred.get_gid_object().save_to_string() == \
889 self.get_gid_object().save_to_string():
890 raise CredentialNotVerifiable("Target gid not equal between parent and child")
892 # make sure my expiry time is <= my parent's
893 if not parent_cred.get_expiration() >= self.get_expiration():
894 raise CredentialNotVerifiable("Delegated credential expires after parent")
896 # make sure my signer is the parent's caller
897 if not parent_cred.get_gid_caller().save_to_string(False) == \
898 self.get_signature().get_issuer_gid().save_to_string(False):
899 raise CredentialNotVerifiable("Delegated credential not signed by parent caller")
902 if parent_cred.parent:
903 parent_cred.verify_parent(parent_cred.parent)
906 def delegate(self, delegee_gidfile, caller_keyfile, caller_gidfile):
908 Return a delegated copy of this credential, delegated to the
909 specified gid's user.
911 # get the gid of the object we are delegating
912 object_gid = self.get_gid_object()
913 object_hrn = object_gid.get_hrn()
915 # the hrn of the user who will be delegated to
916 delegee_gid = GID(filename=delegee_gidfile)
917 delegee_hrn = delegee_gid.get_hrn()
919 #user_key = Keypair(filename=keyfile)
920 #user_hrn = self.get_gid_caller().get_hrn()
921 subject_string = "%s delegated to %s" % (object_hrn, delegee_hrn)
922 dcred = Credential(subject=subject_string)
923 dcred.set_gid_caller(delegee_gid)
924 dcred.set_gid_object(object_gid)
925 dcred.set_parent(self)
926 dcred.set_expiration(self.get_expiration())
927 dcred.set_privileges(self.get_privileges())
928 dcred.get_privileges().delegate_all_privileges(True)
929 #dcred.set_issuer_keys(keyfile, delegee_gidfile)
930 dcred.set_issuer_keys(caller_keyfile, caller_gidfile)
937 def get_filename(self):
938 return getattr(self,'filename',None)
941 # Dump the contents of a credential to stdout in human-readable format
943 # @param dump_parents If true, also dump the parent certificates
944 def dump (self, *args, **kwargs):
945 print self.dump_string(*args, **kwargs)
948 def dump_string(self, dump_parents=False):
950 result += "CREDENTIAL %s\n" % self.get_subject()
951 filename=self.get_filename()
952 if filename: result += "Filename %s\n"%filename
953 result += " privs: %s\n" % self.get_privileges().save_to_string()
954 gidCaller = self.get_gid_caller()
956 result += " gidCaller:\n"
957 result += gidCaller.dump_string(8, dump_parents)
959 if self.get_signature():
961 self.get_signature().get_issuer_gid().dump(8, dump_parents)
963 gidObject = self.get_gid_object()
965 result += " gidObject:\n"
966 result += gidObject.dump_string(8, dump_parents)
968 if self.parent and dump_parents:
970 result += self.parent.dump_string(True)