X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=sfa%2Ftrust%2Fcredential.py;h=109a529011f65f4552165bfa9107b3279a863da7;hb=8a6e87c49dd654bd91158d76c0692b1597593784;hp=54334f22090fc58faaacab5a78f148471fc49840;hpb=ab52244790587248028ae40b9ece1135caf2edc5;p=sfa.git diff --git a/sfa/trust/credential.py b/sfa/trust/credential.py index 54334f22..109a5290 100644 --- a/sfa/trust/credential.py +++ b/sfa/trust/credential.py @@ -26,7 +26,8 @@ # 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 @@ -44,14 +45,13 @@ from xml.parsers.expat import ExpatError from sfa.util.faults import CredentialNotVerifiable, ChildRightsNotSubsetOfParent from sfa.util.sfalogging import logger -from sfa.util.sfatime import utcparse -from sfa.trust.credential_legacy import CredentialLegacy +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.util.xrn import urn_to_hrn, hrn_authfor_hrn -# 2 weeks, in seconds -DEFAULT_CREDENTIAL_LIFETIME = 86400 * 31 +# 31 days, in seconds +DEFAULT_CREDENTIAL_LIFETIME = 86400 * 28 # TODO: @@ -200,9 +200,9 @@ class Signature(object): # 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 @@ -248,7 +248,6 @@ class Credential(object): self.signature = None self.xml = None self.refid = None - self.legacy = None self.type = None self.version = None @@ -263,16 +262,16 @@ class Credential(object): 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 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() @@ -312,24 +311,6 @@ class Credential(object): 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 @@ -384,15 +365,11 @@ class Credential(object): # 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: - 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) @@ -403,11 +380,6 @@ class Credential(object): # 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 # @@ -483,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: + 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) - 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) @@ -540,7 +513,8 @@ 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: - 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) + 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) @@ -679,10 +653,6 @@ class Credential(object): self.xml = signed - # This is no longer a legacy credential - if self.legacy: - self.legacy = None - # Update signatures self.decode() @@ -799,7 +769,7 @@ class Credential(object): self.decode() # validate against RelaxNG schema - if HAVELXML and not self.legacy: + if HAVELXML: if schema and os.path.exists(schema): tree = etree.parse(StringIO(self.xml)) schema_doc = etree.parse(schema) @@ -828,23 +798,14 @@ class Credential(object): logger.error("Failed to load trusted cert from %s: %r"%( f, exc)) 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(): - raise CredentialNotVerifiable("Credential %s expired at %s" % (self.get_summary_tostring(), self.expiration.isoformat())) + raise CredentialNotVerifiable("Credential %s expired at %s" % \ + (self.get_summary_tostring(), + self.expiration.strftime(SFATIME_FORMAT))) # 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. # - Strange and not typical @@ -867,11 +828,25 @@ class Credential(object): 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 = "" @@ -879,7 +854,9 @@ class Credential(object): mstart = mstart + 4 mend = verified.find('\\', mstart) msg = verified[mstart:mend] - raise CredentialNotVerifiable("xmlsec1 error verifying cred %s using Signature ID %s: %s %s" % (self.get_summary_tostring(), 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.get_summary_tostring(), ref, msg)) os.remove(filename) # Verify the parents (delegation) @@ -977,7 +954,8 @@ class Credential(object): # 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())) ## @@ -992,22 +970,26 @@ class Credential(object): # parents rights (and check delegate bits) if not parent_cred.get_privileges().is_superset(self.get_privileges()): 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.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(): - raise CredentialNotVerifiable("Delegated cred %s: Target gid not equal between parent and child. Parent %s" % (self.get_summary_tostring(), parent_cred.get_summary_tostring())) + 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(): - raise CredentialNotVerifiable("Delegated credential %s expires after parent %s" % (self.get_summary_tostring(), parent_cred.get_summary_tostring())) + raise CredentialNotVerifiable("Delegated credential %s expires after parent %s" % \ + (self.get_summary_tostring(), parent_cred.get_summary_tostring())) # 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 %s not signed by parent %s's caller" % (self.get_summary_tostring(), parent_cred.get_summary_tostring())) + raise CredentialNotVerifiable("Delegated credential %s not signed by parent %s's caller" % \ + (self.get_summary_tostring(), parent_cred.get_summary_tostring())) # Recurse if parent_cred.parent: @@ -1101,7 +1083,7 @@ class Credential(object): self.get_signature().get_issuer_gid().dump(8, dump_parents) if self.expiration: - print " expiration:", self.expiration.isoformat() + print " expiration:", self.expiration.strftime(SFATIME_FORMAT) gidObject = self.get_gid_object() if gidObject: