rename get_summary_tostring into pretty_cred
[sfa.git] / sfa / trust / credential.py
index ceed5af..cda6a09 100644 (file)
 # Credentials are signed XML files that assign a subject gid privileges to an object gid
 ##
 
 # Credentials are signed XML files that assign a subject gid privileges to an object gid
 ##
 
-import os
+import os, os.path
+import subprocess
 from types import StringTypes
 import datetime
 from StringIO import StringIO
 from tempfile import mkstemp
 from xml.dom.minidom import Document, parseString
 from types import StringTypes
 import datetime
 from StringIO import StringIO
 from tempfile import mkstemp
 from xml.dom.minidom import Document, parseString
-from lxml import etree
 
 
-from sfa.util.faults import *
+HAVELXML = False
+try:
+    from lxml import etree
+    HAVELXML = True
+except:
+    pass
+
+from xml.parsers.expat import ExpatError
+
+from sfa.util.faults import CredentialNotVerifiable, ChildRightsNotSubsetOfParent
 from sfa.util.sfalogging import logger
 from sfa.util.sfalogging import logger
-from sfa.util.sfatime import utcparse
-from sfa.trust.certificate import Keypair
-from sfa.trust.credential_legacy import CredentialLegacy
-from sfa.trust.rights import Right, Rights
+from sfa.util.sfatime import utcparse, SFATIME_FORMAT
+from sfa.trust.rights import Right, Rights, determine_rights
 from sfa.trust.gid import GID
 from sfa.trust.gid import GID
-from sfa.util.xrn import urn_to_hrn
+from sfa.util.xrn import urn_to_hrn, hrn_authfor_hrn
 
 
-# 2 weeks, in seconds 
-DEFAULT_CREDENTIAL_LIFETIME = 86400 * 14
+# 31 days, in seconds 
+DEFAULT_CREDENTIAL_LIFETIME = 86400 * 28
 
 
 # TODO:
 
 
 # TODO:
@@ -174,7 +181,11 @@ class Signature(object):
         self.gid = gid
 
     def decode(self):
         self.gid = gid
 
     def decode(self):
-        doc = parseString(self.xml)
+        try:
+            doc = parseString(self.xml)
+        except ExpatError,e:
+            logger.log_exc ("Failed to parse credential, %s"%self.xml)
+            raise
         sig = doc.getElementsByTagName("Signature")[0]
         self.set_refid(sig.getAttribute("xml:id").strip("Sig_"))
         keyinfo = sig.getElementsByTagName("X509Data")[0]
         sig = doc.getElementsByTagName("Signature")[0]
         self.set_refid(sig.getAttribute("xml:id").strip("Sig_"))
         keyinfo = sig.getElementsByTagName("X509Data")[0]
@@ -185,31 +196,32 @@ class Signature(object):
     def encode(self):
         self.xml = signature_template % (self.get_refid(), self.get_refid())
 
     def encode(self):
         self.xml = signature_template % (self.get_refid(), self.get_refid())
 
-
 ##
 # A credential provides a caller gid with privileges to an object gid.
 # A signed credential is signed by the object's authority.
 #
 ##
 # A credential provides a caller gid with privileges to an object gid.
 # A signed credential is signed by the object's authority.
 #
-# Credentials are encoded in one of two ways.  The legacy style places
-# it in the subjectAltName of an X509 certificate.  The new credentials
-# are placed in signed XML.
+# Credentials are encoded in one of two ways. 
+# The legacy style (now unsupported) places it in the subjectAltName of an X509 certificate.
+# The new credentials are placed in signed XML.
 #
 # WARNING:
 # In general, a signed credential obtained externally should
 # not be changed else the signature is no longer valid.  So, once
 # you have loaded an existing signed credential, do not call encode() or sign() on it.
 
 #
 # WARNING:
 # In general, a signed credential obtained externally should
 # not be changed else the signature is no longer valid.  So, once
 # you have loaded an existing signed credential, do not call encode() or sign() on it.
 
-def filter_creds_by_caller(creds, caller_hrn):
+def filter_creds_by_caller(creds, caller_hrn_list):
         """
         Returns a list of creds who's gid caller matches the
         specified caller hrn
         """
         if not isinstance(creds, list): creds = [creds]
         """
         Returns a list of creds who's gid caller matches the
         specified caller hrn
         """
         if not isinstance(creds, list): creds = [creds]
+        if not isinstance(caller_hrn_list, list): 
+            caller_hrn_list = [caller_hrn_list]
         caller_creds = []
         for cred in creds:
             try:
                 tmp_cred = Credential(string=cred)
         caller_creds = []
         for cred in creds:
             try:
                 tmp_cred = Credential(string=cred)
-                if tmp_cred.get_gid_caller().get_hrn() == caller_hrn:
+                if tmp_cred.get_gid_caller().get_hrn() in caller_hrn_list:
                     caller_creds.append(cred)
             except: pass
         return caller_creds
                     caller_creds.append(cred)
             except: pass
         return caller_creds
@@ -224,7 +236,7 @@ class Credential(object):
     # @param string If string!=None, load the credential from the string
     # @param filename If filename!=None, load the credential from the file
     # FIXME: create and subject are ignored!
     # @param string If string!=None, load the credential from the string
     # @param filename If filename!=None, load the credential from the file
     # FIXME: create and subject are ignored!
-    def __init__(self, create=False, subject=None, string=None, filename=None):
+    def __init__(self, create=False, subject=None, string=None, filename=None, cred=None):
         self.gidCaller = None
         self.gidObject = None
         self.expiration = None
         self.gidCaller = None
         self.gidObject = None
         self.expiration = None
@@ -236,18 +248,30 @@ class Credential(object):
         self.signature = None
         self.xml = None
         self.refid = None
         self.signature = None
         self.xml = None
         self.refid = None
-        self.legacy = None
+        self.type = None
+        self.version = None
+
+        if cred:
+            if isinstance(cred, StringTypes):
+                string = cred
+                self.type = 'geni_sfa'
+                self.version = '1.0'
+            elif isinstance(cred, dict):
+                string = cred['geni_value']
+                self.type = cred['geni_type']
+                self.version = cred['geni_version']
+                
 
 
-        # Check if this is a legacy credential, translate it if so
         if string or filename:
             if string:                
                 str = string
             elif filename:
                 str = file(filename).read()
                 
         if string or filename:
             if string:                
                 str = string
             elif filename:
                 str = file(filename).read()
                 
-            if str.strip().startswith("-----"):
-                self.legacy = CredentialLegacy(False,string=str)
-                self.translate_legacy(str)
+            # if this is a legacy credential, write error and bail out
+            if isinstance (str, StringTypes) and str.strip().startswith("-----"):
+                logger.error("Legacy credentials not supported any more - giving up with %s..."%str[:10])
+                return
             else:
                 self.xml = str
                 self.decode()
             else:
                 self.xml = str
                 self.decode()
@@ -261,9 +285,22 @@ class Credential(object):
                 break
 
     def get_subject(self):
                 break
 
     def get_subject(self):
+        subject = ""
         if not self.gidObject:
             self.decode()
         if not self.gidObject:
             self.decode()
-        return self.gidObject.get_subject()   
+        if self.gidObject:
+            subject = self.gidObject.get_printable_subject()
+        return subject
+
+    # sounds like this should be __repr__ instead ??
+    def pretty_cred(self):
+        if not self.gidObject:
+            self.decode()
+        obj = self.gidObject.get_printable_subject()
+        caller = self.gidCaller.get_printable_subject()
+        exp = self.get_expiration()
+        # Summarize the rights too? The issuer?
+        return "[ Grant {caller} rights on {obj} until {exp} ]".format(**locals())
 
     def get_signature(self):
         if not self.signature:
 
     def get_signature(self):
         if not self.signature:
@@ -274,24 +311,6 @@ class Credential(object):
         self.signature = sig
 
         
         self.signature = sig
 
         
-    ##
-    # Translate a legacy credential into a new one
-    #
-    # @param String of the legacy credential
-
-    def translate_legacy(self, str):
-        legacy = CredentialLegacy(False,string=str)
-        self.gidCaller = legacy.get_gid_caller()
-        self.gidObject = legacy.get_gid_object()
-        lifetime = legacy.get_lifetime()
-        if not lifetime:
-            self.set_expiration(datetime.datetime.utcnow() + datetime.timedelta(seconds=DEFAULT_CREDENTIAL_LIFETIME))
-        else:
-            self.set_expiration(int(lifetime))
-        self.lifeTime = legacy.get_lifetime()
-        self.set_privileges(legacy.get_privileges())
-        self.get_privileges().delegate_all_privileges(legacy.get_delegate())
-
     ##
     # Need the issuer's private key and name
     # @param key Keypair object containing the private key of the issuer
     ##
     # Need the issuer's private key and name
     # @param key Keypair object containing the private key of the issuer
@@ -341,36 +360,26 @@ class Credential(object):
         if not self.gidObject:
             self.decode()
         return self.gidObject
         if not self.gidObject:
             self.decode()
         return self.gidObject
-
-
             
     ##
     # Expiration: an absolute UTC time of expiration (as either an int or string or datetime)
     # 
     def set_expiration(self, expiration):
             
     ##
     # Expiration: an absolute UTC time of expiration (as either an int or string or datetime)
     # 
     def set_expiration(self, expiration):
-        if isinstance(expiration, (int,float)):
-            self.expiration = datetime.datetime.fromtimestamp(expiration)
-        elif isinstance (expiration, datetime.datetime):
-            self.expiration = expiration
-        elif isinstance (expiration, StringTypes):
-            self.expiration = utcparse (expiration)
+        expiration_datetime = utcparse (expiration)
+        if expiration_datetime is not None:
+            self.expiration = expiration_datetime
         else:
         else:
-            logger.error ("unexpected input type in Credential.set_expiration")
+            logger.error ("unexpected input %s in Credential.set_expiration"%expiration)
 
     ##
     # get the lifetime of the credential (always in datetime format)
 
     ##
     # get the lifetime of the credential (always in datetime format)
-    #
+
     def get_expiration(self):
         if not self.expiration:
             self.decode()
         # at this point self.expiration is normalized as a datetime - DON'T call utcparse again
         return self.expiration
 
     def get_expiration(self):
         if not self.expiration:
             self.decode()
         # at this point self.expiration is normalized as a datetime - DON'T call utcparse again
         return self.expiration
 
-    ##
-    # For legacy sake
-    def get_lifetime(self):
-        return self.get_expiration()
     ##
     # set the privileges
     #
     ##
     # set the privileges
     #
@@ -380,8 +389,7 @@ class Credential(object):
         if isinstance(privs, str):
             self.privileges = Rights(string = privs)
         else:
         if isinstance(privs, str):
             self.privileges = Rights(string = privs)
         else:
-            self.privileges = privs
-        
+            self.privileges = privs        
 
     ##
     # return the privileges as a Rights object
 
     ##
     # return the privileges as a Rights object
@@ -419,13 +427,19 @@ class Credential(object):
         doc = Document()
         signed_cred = doc.createElement("signed-credential")
 
         doc = Document()
         signed_cred = doc.createElement("signed-credential")
 
-# PG adds these. It would be nice to be consistent.
-# But it's kind of odd for PL to use PG schemas that talk
-# about tickets, and the PG CM policies.
-# Note the careful addition of attributes from the parent below...
-#        signed_cred.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance")
-#        signed_cred.setAttribute("xsinoNamespaceSchemaLocation", "http://www.protogeni.net/resources/credential/credential.xsd")
-#        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")
+        # Declare namespaces
+        # Note that credential/policy.xsd are really the PG schemas
+        # in a PL namespace.
+        # Note that delegation of credentials between the 2 only really works
+        # cause those schemas are identical.
+        # Also note these PG schemas talk about PG tickets and CM policies.
+        signed_cred.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance")
+        signed_cred.setAttribute("xsi:noNamespaceSchemaLocation", "http://www.planet-lab.org/resources/sfa/credential.xsd")
+        signed_cred.setAttribute("xsi:schemaLocation", "http://www.planet-lab.org/resources/sfa/ext/policy/1 http://www.planet-lab.org/resources/sfa/ext/policy/1/policy.xsd")
+
+        # PG says for those last 2:
+       #signed_cred.setAttribute("xsi:noNamespaceSchemaLocation", "http://www.protogeni.net/resources/credential/credential.xsd")
+       # 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")
 
         doc.appendChild(signed_cred)  
         
 
         doc.appendChild(signed_cred)  
         
@@ -441,9 +455,10 @@ class Credential(object):
         append_sub(doc, cred, "target_urn", self.gidObject.get_urn())
         append_sub(doc, cred, "uuid", "")
         if not self.expiration:
         append_sub(doc, cred, "target_urn", self.gidObject.get_urn())
         append_sub(doc, cred, "uuid", "")
         if not self.expiration:
+            logger.debug("Creating credential valid for %s s"%DEFAULT_CREDENTIAL_LIFETIME)
             self.set_expiration(datetime.datetime.utcnow() + datetime.timedelta(seconds=DEFAULT_CREDENTIAL_LIFETIME))
         self.expiration = self.expiration.replace(microsecond=0)
             self.set_expiration(datetime.datetime.utcnow() + datetime.timedelta(seconds=DEFAULT_CREDENTIAL_LIFETIME))
         self.expiration = self.expiration.replace(microsecond=0)
-        append_sub(doc, cred, "expires", self.expiration.isoformat())
+        append_sub(doc, cred, "expires", self.expiration.strftime(SFATIME_FORMAT))
         privileges = doc.createElement("privileges")
         cred.appendChild(privileges)
 
         privileges = doc.createElement("privileges")
         cred.appendChild(privileges)
 
@@ -461,13 +476,34 @@ class Credential(object):
             # If the root node is a signed-credential (it should be), then
             # get all its attributes and attach those to our signed_cred
             # node.
             # If the root node is a signed-credential (it should be), then
             # get all its attributes and attach those to our signed_cred
             # node.
-            # Specifically, PG adds attributes for namespaces (which is reasonable),
+            # Specifically, PG and PLadd attributes for namespaces (which is reasonable),
             # and we need to include those again here or else their signature
             # no longer matches on the credential.
             # We expect three of these, but here we copy them all:
             # and we need to include those again here or else their signature
             # no longer matches on the credential.
             # We expect three of these, but here we copy them all:
-#        signed_cred.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance")
-#        signed_cred.setAttribute("xsinoNamespaceSchemaLocation", "http://www.protogeni.net/resources/credential/credential.xsd")
-#        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")
+            #  signed_cred.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance")
+            # and from PG (PL is equivalent, as shown above):
+            #  signed_cred.setAttribute("xsi:noNamespaceSchemaLocation", "http://www.protogeni.net/resources/credential/credential.xsd")
+            #  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")
+
+            # HOWEVER!
+            # PL now also declares these, with different URLs, so
+            # the code notices those attributes already existed with
+            # different values, and complains.
+            # This happens regularly on delegation now that PG and
+            # PL both declare the namespace with different URLs.
+            # If the content ever differs this is a problem,
+            # but for now it works - different URLs (values in the attributes)
+            # but the same actual schema, so using the PG schema
+            # on delegated-to-PL credentials works fine.
+
+            # Note: you could also not copy attributes
+            # which already exist. It appears that both PG and PL
+            # will actually validate a slicecred with a parent
+            # signed using PG namespaces and a child signed with PL
+            # namespaces over the whole thing. But I don't know
+            # if that is a bug in xmlsec1, an accident since
+            # the contents of the schemas are the same,
+            # or something else, but it seems odd. And this works.
             parentRoot = sdoc.documentElement
             if parentRoot.tagName == "signed-credential" and parentRoot.hasAttributes():
                 for attrIx in range(0, parentRoot.attributes.length):
             parentRoot = sdoc.documentElement
             if parentRoot.tagName == "signed-credential" and parentRoot.hasAttributes():
                 for attrIx in range(0, parentRoot.attributes.length):
@@ -477,9 +513,10 @@ class Credential(object):
                     # Below throws InUse exception if we forgot to clone the attribute first
                     oldAttr = signed_cred.setAttributeNode(attr.cloneNode(True))
                     if oldAttr and oldAttr.value != attr.value:
                     # Below throws InUse exception if we forgot to clone the attribute first
                     oldAttr = signed_cred.setAttributeNode(attr.cloneNode(True))
                     if oldAttr and oldAttr.value != attr.value:
-                        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)
-                        logger.error(msg)
-                        raise CredentialNotVerifiable("Can't encode new valid delegated credential: %s" % msg)
+                        msg = "Delegating cred from owner %s to %s over %s:\n - 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)
+                        logger.warn(msg)
+                        #raise CredentialNotVerifiable("Can't encode new valid delegated credential: %s" % msg)
 
             p_cred = doc.importNode(sdoc.getElementsByTagName("credential")[0], True)
             p = doc.createElement("parent")
 
             p_cred = doc.importNode(sdoc.getElementsByTagName("credential")[0], True)
             p = doc.createElement("parent")
@@ -616,10 +653,6 @@ class Credential(object):
 
         self.xml = signed
 
 
         self.xml = signed
 
-        # This is no longer a legacy credential
-        if self.legacy:
-            self.legacy = None
-
         # Update signatures
         self.decode()       
 
         # Update signatures
         self.decode()       
 
@@ -632,19 +665,31 @@ class Credential(object):
     def decode(self):
         if not self.xml:
             return
     def decode(self):
         if not self.xml:
             return
+
+        doc = None
+        try:
+            doc = parseString(self.xml)
+        except ExpatError,e:
+            raise CredentialNotVerifiable("Malformed credential")
         doc = parseString(self.xml)
         sigs = []
         signed_cred = doc.getElementsByTagName("signed-credential")
 
         # Is this a signed-cred or just a cred?
         if len(signed_cred) > 0:
         doc = parseString(self.xml)
         sigs = []
         signed_cred = doc.getElementsByTagName("signed-credential")
 
         # Is this a signed-cred or just a cred?
         if len(signed_cred) > 0:
-            cred = signed_cred[0].getElementsByTagName("credential")[0]
+            creds = signed_cred[0].getElementsByTagName("credential")
             signatures = signed_cred[0].getElementsByTagName("signatures")
             if len(signatures) > 0:
                 sigs = signatures[0].getElementsByTagName("Signature")
         else:
             signatures = signed_cred[0].getElementsByTagName("signatures")
             if len(signatures) > 0:
                 sigs = signatures[0].getElementsByTagName("Signature")
         else:
-            cred = doc.getElementsByTagName("credential")[0]
+            creds = doc.getElementsByTagName("credential")
         
         
+        if creds is None or len(creds) == 0:
+            # malformed cred file
+            raise CredentialNotVerifiable("Malformed XML: No credential tag found")
+
+        # Just take the first cred if there are more than one
+        cred = creds[0]
 
         self.set_refid(cred.getAttribute("xml:id"))
         self.set_expiration(utcparse(getTextNode(cred, "expires")))
 
         self.set_refid(cred.getAttribute("xml:id"))
         self.set_expiration(utcparse(getTextNode(cred, "expires")))
@@ -662,7 +707,7 @@ class Credential(object):
                 # Convert * into the default privileges for the credential's type
                 # Each inherits the delegatability from the * above
                 _ , type = urn_to_hrn(self.gidObject.get_urn())
                 # Convert * into the default privileges for the credential's type
                 # Each inherits the delegatability from the * above
                 _ , type = urn_to_hrn(self.gidObject.get_urn())
-                rl = rlist.determine_rights(type, self.gidObject.get_urn())
+                rl = determine_rights(type, self.gidObject.get_urn())
                 for r in rl.rights:
                     r.delegate = deleg
                     rlist.add(r)
                 for r in rl.rights:
                     r.delegate = deleg
                     rlist.add(r)
@@ -704,6 +749,7 @@ class Credential(object):
     # . That the issuer of the credential is the authority in the target's urn
     #    . In the case of a delegated credential, this must be true of the root
     # . That all of the gids presented in the credential are valid
     # . That the issuer of the credential is the authority in the target's urn
     #    . In the case of a delegated credential, this must be true of the root
     # . That all of the gids presented in the credential are valid
+    #    . Including verifying GID chains, and includ the issuer
     # . The credential is not expired
     #
     # -- For Delegates (credentials with parents)
     # . The credential is not expired
     #
     # -- For Delegates (credentials with parents)
@@ -723,15 +769,15 @@ class Credential(object):
             self.decode()
 
         # validate against RelaxNG schema
             self.decode()
 
         # validate against RelaxNG schema
-        if not self.legacy:
+        if HAVELXML:
             if schema and os.path.exists(schema):
                 tree = etree.parse(StringIO(self.xml))
                 schema_doc = etree.parse(schema)
                 xmlschema = etree.XMLSchema(schema_doc)
                 if not xmlschema.validate(tree):
                     error = xmlschema.error_log.last_error
             if schema and os.path.exists(schema):
                 tree = etree.parse(StringIO(self.xml))
                 schema_doc = etree.parse(schema)
                 xmlschema = etree.XMLSchema(schema_doc)
                 if not xmlschema.validate(tree):
                     error = xmlschema.error_log.last_error
-                    message = "%s (line %s)" % (error.message, error.line)
-                    raise CredentialNotVerifiable(message)        
+                    message = "%s: %s (line %s)" % (self.pretty_cred(), error.message, error.line)
+                    raise CredentialNotVerifiable(message)
 
         if trusted_certs_required and trusted_certs is None:
             trusted_certs = []
 
         if trusted_certs_required and trusted_certs is None:
             trusted_certs = []
@@ -749,29 +795,20 @@ class Credential(object):
                     trusted_cert_objects.append(GID(filename=f))
                     ok_trusted_certs.append(f)
                 except Exception, exc:
                     trusted_cert_objects.append(GID(filename=f))
                     ok_trusted_certs.append(f)
                 except Exception, exc:
-                    logger.error("Failed to load trusted cert from %s: %r", f, exc)
+                    logger.error("Failed to load trusted cert from %s: %r"%( f, exc))
             trusted_certs = ok_trusted_certs
 
             trusted_certs = ok_trusted_certs
 
-        # Use legacy verification if this is a legacy credential
-        if self.legacy:
-            self.legacy.verify_chain(trusted_cert_objects)
-            if self.legacy.client_gid:
-                self.legacy.client_gid.verify_chain(trusted_cert_objects)
-            if self.legacy.object_gid:
-                self.legacy.object_gid.verify_chain(trusted_cert_objects)
-            return True
-        
         # make sure it is not expired
         if self.get_expiration() < datetime.datetime.utcnow():
         # make sure it is not expired
         if self.get_expiration() < datetime.datetime.utcnow():
-            raise CredentialNotVerifiable("Credential expired at %s" % self.expiration.isoformat())
+            raise CredentialNotVerifiable("Credential %s expired at %s" % \
+                                          (self.pretty_cred(),
+                                           self.expiration.strftime(SFATIME_FORMAT)))
 
         # Verify the signatures
         filename = self.save_to_random_tmp_file()
 
         # Verify the signatures
         filename = self.save_to_random_tmp_file()
-        if trusted_certs is not None:
-            cert_args = " ".join(['--trusted-pem %s' % x for x in trusted_certs])
 
         # If caller explicitly passed in None that means skip cert chain validation.
 
         # If caller explicitly passed in None that means skip cert chain validation.
-        # Strange and not typical
+        # Strange and not typical
         if trusted_certs is not None:
             # Verify the gids of this cred and of its parents
             for cur_cred in self.get_credential_list():
         if trusted_certs is not None:
             # Verify the gids of this cred and of its parents
             for cur_cred in self.get_credential_list():
@@ -791,11 +828,25 @@ class Credential(object):
             if trusted_certs is None:
                 break
 
             if trusted_certs is None:
                 break
 
-#            print "Doing %s --verify --node-id '%s' %s %s 2>&1" % \
-#                (self.xmlsec_path, ref, cert_args, filename)
-            verified = os.popen('%s --verify --node-id "%s" %s %s 2>&1' \
-                            % (self.xmlsec_path, ref, cert_args, filename)).read()
-            if not verified.strip().startswith("OK"):
+            # Thierry - jan 2015
+            # up to fedora20 we used os.popen and checked that the output begins with OK
+            # turns out, with fedora21, there is extra input before this 'OK' thing
+            # looks like we're better off just using the exit code - that's what it is made for
+            #cert_args = " ".join(['--trusted-pem %s' % x for x in trusted_certs])
+            #command = '{} --verify --node-id "{}" {} {} 2>&1'.\
+            #          format(self.xmlsec_path, ref, cert_args, filename)
+            command = [ self.xmlsec_path, '--verify', '--node-id', ref ]
+            for trusted in trusted_certs:
+                command += ["--trusted-pem", trusted ]
+            command += [ filename ]
+            logger.debug("Running " + " ".join(command))
+            try:
+                verified = subprocess.check_output(command, stderr=subprocess.STDOUT)
+                logger.debug("xmlsec command returned {}".format(verified))
+                if "OK\n" not in verified:
+                    logger.warning("WARNING: xmlsec1 seemed to return fine but without a OK in its output")
+            except subprocess.CalledProcessError as e:
+                verified = e.output
                 # xmlsec errors have a msg= which is the interesting bit.
                 mstart = verified.find("msg=")
                 msg = ""
                 # xmlsec errors have a msg= which is the interesting bit.
                 mstart = verified.find("msg=")
                 msg = ""
@@ -803,15 +854,18 @@ class Credential(object):
                     mstart = mstart + 4
                     mend = verified.find('\\', mstart)
                     msg = verified[mstart:mend]
                     mstart = mstart + 4
                     mend = verified.find('\\', mstart)
                     msg = verified[mstart:mend]
-                raise CredentialNotVerifiable("xmlsec1 error verifying cred using Signature ID %s: %s %s" % (ref, msg, verified.strip()))
+                logger.warning("Credential.verify - failed - xmlsec1 returned {}".format(verified.strip()))
+                raise CredentialNotVerifiable("xmlsec1 error verifying cred %s using Signature ID %s: %s" % \
+                                              (self.pretty_cred(), ref, msg))
         os.remove(filename)
 
         # Verify the parents (delegation)
         if self.parent:
             self.verify_parent(self.parent)
 
         os.remove(filename)
 
         # Verify the parents (delegation)
         if self.parent:
             self.verify_parent(self.parent)
 
-        # Make sure the issuer is the target's authority
-        self.verify_issuer()
+        # Make sure the issuer is the target's authority, and is
+        # itself a valid GID
+        self.verify_issuer(trusted_cert_objects)
         return True
 
     ##
         return True
 
     ##
@@ -829,31 +883,67 @@ class Credential(object):
         return list
     
     ##
         return list
     
     ##
-    # Make sure the credential's target gid was signed by (or is the same) the entity that signed
-    # the original credential or an authority over that namespace.
-    def verify_issuer(self):                
+    # Make sure the credential's target gid (a) was signed by or (b)
+    # is the same as the entity that signed the original credential,
+    # or (c) is an authority over the target's namespace.
+    # Also ensure that the credential issuer / signer itself has a valid
+    # GID signature chain (signed by an authority with namespace rights).
+    def verify_issuer(self, trusted_gids):
         root_cred = self.get_credential_list()[-1]
         root_target_gid = root_cred.get_gid_object()
         root_cred_signer = root_cred.get_signature().get_issuer_gid()
 
         root_cred = self.get_credential_list()[-1]
         root_target_gid = root_cred.get_gid_object()
         root_cred_signer = root_cred.get_signature().get_issuer_gid()
 
+        # Case 1:
+        # Allow non authority to sign target and cred about target.
+        #
+        # Why do we need to allow non authorities to sign?
+        # If in the target gid validation step we correctly
+        # checked that the target is only signed by an authority,
+        # then this is just a special case of case 3.
+        # This short-circuit is the common case currently -
+        # and cause GID validation doesn't check 'authority',
+        # this allows users to generate valid slice credentials.
         if root_target_gid.is_signed_by_cert(root_cred_signer):
             # cred signer matches target signer, return success
             return
 
         if root_target_gid.is_signed_by_cert(root_cred_signer):
             # cred signer matches target signer, return success
             return
 
-        root_target_gid_str = root_target_gid.save_to_string()
-        root_cred_signer_str = root_cred_signer.save_to_string()
-        if root_target_gid_str == root_cred_signer_str:
-            # cred signer is target, return success
-            return
+        # Case 2:
+        # Allow someone to sign credential about themeselves. Used?
+        # If not, remove this.
+        #root_target_gid_str = root_target_gid.save_to_string()
+        #root_cred_signer_str = root_cred_signer.save_to_string()
+        #if root_target_gid_str == root_cred_signer_str:
+        #    # cred signer is target, return success
+        #    return
+
+        # Case 3:
+
+        # root_cred_signer is not the target_gid
+        # So this is a different gid that we have not verified.
+        # xmlsec1 verified the cert chain on this already, but
+        # it hasn't verified that the gid meets the HRN namespace
+        # requirements.
+        # Below we'll ensure that it is an authority.
+        # But we haven't verified that it is _signed by_ an authority
+        # We also don't know if xmlsec1 requires that cert signers
+        # are marked as CAs.
+
+        # Note that if verify() gave us no trusted_gids then this
+        # call will fail. So skip it if we have no trusted_gids
+        if trusted_gids and len(trusted_gids) > 0:
+            root_cred_signer.verify_chain(trusted_gids)
+        else:
+            logger.debug("No trusted gids. Cannot verify that cred signer is signed by a trusted authority. Skipping that check.")
 
 
-        # See if it the signer is an authority over the domain of the target
+        # See if the signer is an authority over the domain of the target.
+        # There are multiple types of authority - accept them all here
         # Maybe should be (hrn, type) = urn_to_hrn(root_cred_signer.get_urn())
         root_cred_signer_type = root_cred_signer.get_type()
         # Maybe should be (hrn, type) = urn_to_hrn(root_cred_signer.get_urn())
         root_cred_signer_type = root_cred_signer.get_type()
-        if (root_cred_signer_type == 'authority'):
-            #sfa_logger.debug('Cred signer is an authority')
+        if (root_cred_signer_type.find('authority') == 0):
+            #logger.debug('Cred signer is an authority')
             # signer is an authority, see if target is in authority's domain
             # signer is an authority, see if target is in authority's domain
-            hrn = root_cred_signer.get_hrn()
-            if root_target_gid.get_hrn().startswith(hrn):
+            signerhrn = root_cred_signer.get_hrn()
+            if hrn_authfor_hrn(signerhrn, root_target_gid.get_hrn()):
                 return
 
         # We've required that the credential be signed by an authority
                 return
 
         # We've required that the credential be signed by an authority
@@ -864,7 +954,8 @@ class Credential(object):
 
         # Give up, credential does not pass issuer verification
 
 
         # Give up, credential does not pass issuer verification
 
-        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()))
+        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()))
 
 
     ##
 
 
     ##
@@ -878,23 +969,27 @@ class Credential(object):
         # make sure the rights given to the child are a subset of the
         # parents rights (and check delegate bits)
         if not parent_cred.get_privileges().is_superset(self.get_privileges()):
         # make sure the rights given to the child are a subset of the
         # parents rights (and check delegate bits)
         if not parent_cred.get_privileges().is_superset(self.get_privileges()):
-            raise ChildRightsNotSubsetOfParent(("Parent cred ref %s rights " % self.parent.get_refid()) + 
-                self.parent.get_privileges().save_to_string() + (" not superset of delegated cred ref %s rights " % self.get_refid()) +
+            raise ChildRightsNotSubsetOfParent(("Parent cred ref %s rights " % parent_cred.get_refid()) +
+                self.parent.get_privileges().save_to_string() + (" not superset of delegated cred %s ref %s rights " % \
+                                                                 (self.get_summary_tostring(), self.get_refid())) +
                 self.get_privileges().save_to_string())
 
         # make sure my target gid is the same as the parent's
         if not parent_cred.get_gid_object().save_to_string() == \
            self.get_gid_object().save_to_string():
                 self.get_privileges().save_to_string())
 
         # make sure my target gid is the same as the parent's
         if not parent_cred.get_gid_object().save_to_string() == \
            self.get_gid_object().save_to_string():
-            raise CredentialNotVerifiable("Target gid not equal between parent and child")
+            raise CredentialNotVerifiable("Delegated cred %s: Target gid not equal between parent and child. Parent %s" % \
+                                          (self.get_summary_tostring(), parent_cred.get_summary_tostring()))
 
         # make sure my expiry time is <= my parent's
         if not parent_cred.get_expiration() >= self.get_expiration():
 
         # make sure my expiry time is <= my parent's
         if not parent_cred.get_expiration() >= self.get_expiration():
-            raise CredentialNotVerifiable("Delegated credential expires after parent")
+            raise CredentialNotVerifiable("Delegated credential %s expires after parent %s" % \
+                                          (self.pretty_cred(), parent_cred.pretty_cred()))
 
         # make sure my signer is the parent's caller
         if not parent_cred.get_gid_caller().save_to_string(False) == \
            self.get_signature().get_issuer_gid().save_to_string(False):
 
         # make sure my signer is the parent's caller
         if not parent_cred.get_gid_caller().save_to_string(False) == \
            self.get_signature().get_issuer_gid().save_to_string(False):
-            raise CredentialNotVerifiable("Delegated credential not signed by parent caller")
+            raise CredentialNotVerifiable("Delegated credential %s not signed by parent %s's caller" % \
+                                          (self.pretty_cred(), parent_cred.pretty_cred()))
                 
         # Recurse
         if parent_cred.parent:
                 
         # Recurse
         if parent_cred.parent:
@@ -934,7 +1029,33 @@ class Credential(object):
     # only informative
     def get_filename(self):
         return getattr(self,'filename',None)
     # only informative
     def get_filename(self):
         return getattr(self,'filename',None)
+    
+    def actual_caller_hrn (self):
+        """a helper method used by some API calls like e.g. Allocate
+        to try and find out who really is the original caller
+        
+        This admittedly is a bit of a hack, please USE IN LAST RESORT
+        
+        This code uses a heuristic to identify a delegated credential
+
+        A first known restriction if for traffic that gets through a slice manager
+        in this case the hrn reported is the one from the last SM in the call graph
+        which is not at all what is meant here"""
+
+        caller_hrn = self.get_gid_caller().get_hrn()
+        issuer_hrn = self.get_signature().get_issuer_gid().get_hrn()
+        subject_hrn = self.get_gid_object().get_hrn()
+        # if we find that the caller_hrn is an immediate descendant of the issuer, then
+        # this seems to be a 'regular' credential
+        if caller_hrn.startswith(issuer_hrn): 
+            actual_caller_hrn=caller_hrn
+        # else this looks like a delegated credential, and the real caller is the issuer
+        else:
+            actual_caller_hrn=issuer_hrn
+        logger.info("actual_caller_hrn: caller_hrn=%s, issuer_hrn=%s, returning %s"
+                    %(caller_hrn,issuer_hrn,actual_caller_hrn))
+        return actual_caller_hrn
+            
     ##
     # Dump the contents of a credential to stdout in human-readable format
     #
     ##
     # Dump the contents of a credential to stdout in human-readable format
     #
@@ -942,13 +1063,17 @@ class Credential(object):
     def dump (self, *args, **kwargs):
         print self.dump_string(*args, **kwargs)
 
     def dump (self, *args, **kwargs):
         print self.dump_string(*args, **kwargs)
 
-
-    def dump_string(self, dump_parents=False):
+    # show_xml is ignored
+    def dump_string(self, dump_parents=False, show_xml=None):
         result=""
         result += "CREDENTIAL %s\n" % self.get_subject()
         filename=self.get_filename()
         if filename: result += "Filename %s\n"%filename
         result=""
         result += "CREDENTIAL %s\n" % self.get_subject()
         filename=self.get_filename()
         if filename: result += "Filename %s\n"%filename
-        result += "      privs: %s\n" % self.get_privileges().save_to_string()
+        privileges = self.get_privileges()
+        if privileges:
+            result += "      privs: %s\n" % privileges.save_to_string()
+        else:
+            result += "      privs: \n" 
         gidCaller = self.get_gid_caller()
         if gidCaller:
             result += "  gidCaller:\n"
         gidCaller = self.get_gid_caller()
         if gidCaller:
             result += "  gidCaller:\n"
@@ -958,6 +1083,9 @@ class Credential(object):
             print "  gidIssuer:"
             self.get_signature().get_issuer_gid().dump(8, dump_parents)
 
             print "  gidIssuer:"
             self.get_signature().get_issuer_gid().dump(8, dump_parents)
 
+        if self.expiration:
+            print "  expiration:", self.expiration.strftime(SFATIME_FORMAT)
+
         gidObject = self.get_gid_object()
         if gidObject:
             result += "  gidObject:\n"
         gidObject = self.get_gid_object()
         if gidObject:
             result += "  gidObject:\n"