Example #1
0
    def delegate(self, delegee_gidfile, caller_keyfile, caller_gidfile):
        """
        Return a delegated copy of this credential, delegated to the 
        specified gid's user.    
        """
        # get the gid of the object we are delegating
        object_gid = self.get_gid_object()
        object_hrn = object_gid.get_hrn()

        # the hrn of the user who will be delegated to
        delegee_gid = GID(filename=delegee_gidfile)
        delegee_hrn = delegee_gid.get_hrn()

        #user_key = Keypair(filename=keyfile)
        #user_hrn = self.get_gid_caller().get_hrn()
        subject_string = "%s delegated to %s" % (object_hrn, delegee_hrn)
        dcred = Credential(subject=subject_string)
        dcred.set_gid_caller(delegee_gid)
        dcred.set_gid_object(object_gid)
        dcred.set_parent(self)
        dcred.set_expiration(self.get_expiration())
        dcred.set_privileges(self.get_privileges())
        dcred.get_privileges().delegate_all_privileges(True)
        #dcred.set_issuer_keys(keyfile, delegee_gidfile)
        dcred.set_issuer_keys(caller_keyfile, caller_gidfile)
        dcred.encode()
        dcred.sign()

        return dcred
Example #2
0
    def decode(self):
        data = self.get_data().lstrip('URI:http://')

        if data:
            dict = xmlrpclib.loads(data)[0][0]
        else:
            dict = {}

        self.lifeTime = dict.get("lifeTime", None)
        self.delegate = dict.get("delegate", None)

        privStr = dict.get("privileges", None)
        if privStr:
            self.privileges = Rights(string=privStr)
        else:
            self.privileges = None

        gidCallerStr = dict.get("gidCaller", None)
        if gidCallerStr:
            self.gidCaller = GID(string=gidCallerStr)
        else:
            self.gidCaller = None

        gidObjectStr = dict.get("gidObject", None)
        if gidObjectStr:
            self.gidObject = GID(string=gidObjectStr)
        else:
            self.gidObject = None
Example #3
0
    def delegate(self, delegee_gidfile, caller_keyfile, caller_gidfile):
        """
        Return a delegated copy of this credential, delegated to the 
        specified gid's user.    
        """
        # get the gid of the object we are delegating
        object_gid = self.get_gid_object()
        object_hrn = object_gid.get_hrn()        
 
        # the hrn of the user who will be delegated to
        delegee_gid = GID(filename=delegee_gidfile)
        delegee_hrn = delegee_gid.get_hrn()
  
        #user_key = Keypair(filename=keyfile)
        #user_hrn = self.get_gid_caller().get_hrn()
        subject_string = "%s delegated to %s" % (object_hrn, delegee_hrn)
        dcred = Credential(subject=subject_string)
        dcred.set_gid_caller(delegee_gid)
        dcred.set_gid_object(object_gid)
        dcred.set_parent(self)
        dcred.set_expiration(self.get_expiration())
        dcred.set_privileges(self.get_privileges())
        dcred.get_privileges().delegate_all_privileges(True)
        #dcred.set_issuer_keys(keyfile, delegee_gidfile)
        dcred.set_issuer_keys(caller_keyfile, caller_gidfile)
        dcred.encode()
        dcred.sign()

        return dcred
Example #4
0
def verify_certificate(certificate, trusted_cert_path=None, crl_path=None):
    """
    Taken from ext...gid
    Verifies the chain of authenticity of the GID. First performs the checks of the certificate class (verifying that each parent signs the child, etc).
    In addition, GIDs also confirm that the parent's HRN is a prefix of the child's HRN, and the parent is of type 'authority'.

    Raises a ValueError if bad certificate.
    Does not return anything if successful.
    """
    try:
        trusted_certs = None
        if trusted_cert_path:
            trusted_certs_paths = [
                os.path.join(os.path.expanduser(trusted_cert_path), name)
                for name in os.listdir(os.path.expanduser(trusted_cert_path))
                if (name != gcf_cred_util.CredentialVerifier.CATEDCERTSFNAME)
                and (name[0] != '.')
            ]
            trusted_certs = [
                GID(filename=name) for name in trusted_certs_paths
            ]
        gid = GID(string=certificate)
        gid.verify_chain(trusted_certs, crl_path)
    except SfaFault as e:
        raise ValueError("Error verifying certificate: %s" % (str(e), ))
    return None
Example #5
0
def create_credential(owner_cert, target_cert, issuer_key, issuer_cert, typ, expiration, delegatable=False):
    """
    {expiration} can be a datetime.datetime or a int/float (see http://docs.python.org/2/library/datetime.html#datetime.date.fromtimestamp) or a string with a UTC timestamp in it
    {typ} is used to determine the rights (via ext/sfa/truse/rights.py) can either of the following: "user", "sa", "ma", "cm", "sm", "authority", "slice", "component" also you may specify "admin" for all privileges.
    Returns the credential as String
    """
    ucred = sfa_cred.Credential()
    ucred.set_gid_caller(GID(string=owner_cert))
    ucred.set_gid_object(GID(string=target_cert))
    ucred.set_expiration(expiration)

    if typ == "admin":
        if delegatable:
            raise ValueError("Admin credentials can not be delegatable")
        privileges = sfa_rights.Rights("*")
    else:
        privileges = sfa_rights.determine_rights(typ, None)
        privileges.delegate_all_privileges(delegatable)
    ucred.set_privileges(privileges)
    ucred.encode()

    issuer_key_file, issuer_key_filename = tempfile.mkstemp(); os.write(issuer_key_file, issuer_key); os.close(issuer_key_file)
    issuer_cert_file, issuer_cert_filename = tempfile.mkstemp(); os.write(issuer_cert_file, issuer_cert); os.close(issuer_cert_file)

    ucred.set_issuer_keys(issuer_key_filename, issuer_cert_filename) # priv, gid
    ucred.sign()

    os.remove(issuer_key_filename)
    os.remove(issuer_cert_filename)

    return ucred.save_to_string()
Example #6
0
def extract_certificate_info(certificate):
    """Returns the urn, uuid and email of the given certificate."""
    user_gid = GID(string=certificate)
    user_urn = user_gid.get_urn()
    user_uuid = user_gid.get_uuid()
    user_email = user_gid.get_email()
    return user_urn, user_uuid, user_email
    def sign(self):
        if not self.issuer_privkey:
            logger.warn("Cannot sign credential (no private key)")
            return
        if not self.issuer_gid:
            logger.warn("Cannot sign credential (no issuer gid)")
            return
        doc = parseString(self.get_xml())
        sigs = doc.getElementsByTagName("signatures")[0]

        # Create the signature template to be signed
        signature = Signature()
        signature.set_refid(self.get_refid())
        sdoc = parseString(signature.get_xml())        
        sig_ele = doc.importNode(sdoc.getElementsByTagName("Signature")[0], True)
        sigs.appendChild(sig_ele)

        self.xml = doc.toxml()


        # Split the issuer GID into multiple certificates if it's a chain
        chain = GID(filename=self.issuer_gid)
        gid_files = []
        while chain:
            gid_files.append(chain.save_to_random_tmp_file(False))
            if chain.get_parent():
                chain = chain.get_parent()
            else:
                chain = None


        # Call out to xmlsec1 to sign it
        ref = 'Sig_%s' % self.get_refid()
        filename = self.save_to_random_tmp_file()
        command='%s --sign --node-id "%s" --privkey-pem %s,%s %s' \
            % (self.xmlsec_path, ref, self.issuer_privkey, ",".join(gid_files), filename)
#        print 'command',command
        signed = os.popen(command).read()
        os.remove(filename)

        for gid_file in gid_files:
            os.remove(gid_file)

        self.xml = signed

        # This is no longer a legacy credential
        if self.legacy:
            self.legacy = None

        # Update signatures
        self.decode()       
Example #8
0
    def sign(self):
        if not self.issuer_privkey:
            logger.warn("Cannot sign credential (no private key)")
            return
        if not self.issuer_gid:
            logger.warn("Cannot sign credential (no issuer gid)")
            return
        doc = parseString(self.get_xml())
        sigs = doc.getElementsByTagName("signatures")[0]

        # Create the signature template to be signed
        signature = Signature()
        signature.set_refid(self.get_refid())
        sdoc = parseString(signature.get_xml())        
        sig_ele = doc.importNode(sdoc.getElementsByTagName("Signature")[0], True)
        sigs.appendChild(sig_ele)

        self.xml = doc.toxml()


        # Split the issuer GID into multiple certificates if it's a chain
        chain = GID(filename=self.issuer_gid)
        gid_files = []
        while chain:
            gid_files.append(chain.save_to_random_tmp_file(False))
            if chain.get_parent():
                chain = chain.get_parent()
            else:
                chain = None


        # Call out to xmlsec1 to sign it
        ref = 'Sig_%s' % self.get_refid()
        filename = self.save_to_random_tmp_file()
        command='%s --sign --node-id "%s" --privkey-pem %s,%s %s' \
            % (self.xmlsec_path, ref, self.issuer_privkey, ",".join(gid_files), filename)
#        print 'command',command
        signed = os.popen(command).read()
        os.remove(filename)

        for gid_file in gid_files:
            os.remove(gid_file)

        self.xml = signed

        # This is no longer a legacy credential
        if self.legacy:
            self.legacy = None

        # Update signatures
        self.decode()       
Example #9
0
    def verify(self, trusted_certs=None, schema=None, trusted_certs_required=True):
        if not self.xml:
            self.decode()

        # validate against RelaxNG schema
        if HAVELXML and not self.legacy:
            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: %s (line %s)" % (self.get_summary_tostring(), error.message, error.line)
                    raise CredentialNotVerifiable(message)

        if trusted_certs_required and trusted_certs is None:
            trusted_certs = []

#        trusted_cert_objects = [GID(filename=f) for f in trusted_certs]
        trusted_cert_objects = []
        ok_trusted_certs = []
        # If caller explicitly passed in None that means skip cert chain validation.
        # Strange and not typical
        if trusted_certs is not None:
            for f in trusted_certs:
                try:
                    # Failures here include unreadable files
                    # or non PEM files
                    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))
            trusted_certs = ok_trusted_certs
Example #10
0
def create_credential_ex(owner_cert,
                         target_cert,
                         issuer_key,
                         issuer_cert,
                         privileges_list,
                         expiration,
                         delegatable=True,
                         parent_creds=None):
    """
    {expiration} can be a datetime.datetime or a int/float (see http://docs.python.org/2/library/datetime.html#datetime.date.fromtimestamp) or a string with a UTC timestamp in it
    {privileges_list} list of privileges as list
    Returns the credential as String
    """
    ucred = sfa_cred.Credential()
    ucred.set_gid_caller(GID(string=owner_cert))
    ucred.set_gid_object(GID(string=target_cert))
    ucred.set_expiration(expiration)

    #Check if delegated credentials have been requested
    if parent_creds:
        cred_obj = sfa_cred.Credential(string=parent_creds[0]['SFA'])
        ucred.set_parent(cred_obj)

    privileges_str = ','.join(privileges_list)
    privileges = sfa_rights.Rights(privileges_str)
    privileges.delegate_all_privileges(delegatable)
    ucred.set_privileges(privileges)
    ucred.encode()

    issuer_key_file, issuer_key_filename = tempfile.mkstemp()
    os.write(issuer_key_file, issuer_key)
    os.close(issuer_key_file)
    issuer_cert_file, issuer_cert_filename = tempfile.mkstemp()
    os.write(issuer_cert_file, issuer_cert)
    os.close(issuer_cert_file)

    ucred.set_issuer_keys(issuer_key_filename,
                          issuer_cert_filename)  # priv, gid
    ucred.sign()

    os.remove(issuer_key_filename)
    os.remove(issuer_cert_filename)

    return ucred.save_to_string()
Example #11
0
def verify_certificate(certificate, trusted_cert_path=None):
    """
    Taken from ext...gid
    Verifies the chain of authenticity of the GID. First performs the checks of the certificate class (verifying that each parent signs the child, etc).
    In addition, GIDs also confirm that the parent's HRN is a prefix of the child's HRN, and the parent is of type 'authority'.

    Raises a ValueError if bad certificate.
    Does not return anything if successful.
    """
    try:
        trusted_certs = None
        if trusted_cert_path:
            trusted_certs_paths = [os.path.join(os.path.expanduser(trusted_cert_path), name) for name in os.listdir(os.path.expanduser(trusted_cert_path)) if (name != gcf_cred_util.CredentialVerifier.CATEDCERTSFNAME) and (name[0] != '.')]
            trusted_certs = [GID(filename=name) for name in trusted_certs_paths]
        gid = GID(string=certificate)
        gid.verify_chain(trusted_certs)
    except SfaFault as e:
        raise ValueError("Error verifying certificate: %s" % (str(e),))
    return None
Example #12
0
class Signature(object):
   
    def __init__(self, string=None):
        self.refid = None
        self.issuer_gid = None
        self.xml = None
        if string:
            self.xml = string
            self.decode()


    def get_refid(self):
        if not self.refid:
            self.decode()
        return self.refid

    def get_xml(self):
        if not self.xml:
            self.encode()
        return self.xml

    def set_refid(self, id):
        self.refid = id

    def get_issuer_gid(self):
        if not self.gid:
            self.decode()
        return self.gid        

    def set_issuer_gid(self, gid):
        self.gid = gid

    def decode(self):
        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]
        szgid = getTextNode(keyinfo, "X509Certificate")
        szgid = "-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----" % szgid
        self.set_issuer_gid(GID(string=szgid))        
Example #13
0
class Credential(object):

    ##
    # Create a Credential object
    #
    # @param create If true, create a blank x509 certificate
    # @param subject If subject!=None, create an x509 cert with the subject name
    # @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):
        self.gidCaller = None
        self.gidObject = None
        self.expiration = None
        self.privileges = None
        self.issuer_privkey = None
        self.issuer_gid = None
        self.issuer_pubkey = None
        self.parent = None
        self.signature = None
        self.xml = None
        self.refid = None
        self.legacy = None

        # 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)
            else:
                self.xml = str
                self.decode()

        # Find an xmlsec1 path
        self.xmlsec_path = ''
        paths = ['/usr/bin','/usr/local/bin','/bin','/opt/bin','/opt/local/bin']
        for path in paths:
            if os.path.isfile(path + '/' + 'xmlsec1'):
                self.xmlsec_path = path + '/' + 'xmlsec1'
                break
        if not self.xmlsec_path:
            logger.warn("Could not locate binary for xmlsec1 - SFA will be unable to sign stuff !!")

    def get_subject(self):
        if not self.gidObject:
            self.decode()
        return self.gidObject.get_subject()

    # sounds like this should be __repr__ instead ??
    def get_summary_tostring(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 %s rights on %s until %s ]" % (caller, obj, exp)

    def get_signature(self):
        if not self.signature:
            self.decode()
        return self.signature

    def set_signature(self, 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
    # @param gid GID of the issuing authority

    def set_issuer_keys(self, privkey, gid):
        self.issuer_privkey = privkey
        self.issuer_gid = gid


    ##
    # Set this credential's parent
    def set_parent(self, cred):
        self.parent = cred
        self.updateRefID()

    ##
    # set the GID of the caller
    #
    # @param gid GID object of the caller

    def set_gid_caller(self, gid):
        self.gidCaller = gid
        # gid origin caller is the caller's gid by default
        self.gidOriginCaller = gid

    ##
    # get the GID of the object

    def get_gid_caller(self):
        if not self.gidCaller:
            self.decode()
        return self.gidCaller

    ##
    # set the GID of the object
    #
    # @param gid GID object of the object

    def set_gid_object(self, gid):
        self.gidObject = gid

    ##
    # get the GID of the object

    def get_gid_object(self):
        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):
        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)
        else:
            logger.error ("unexpected input type in Credential.set_expiration")


    ##
    # 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

    ##
    # For legacy sake
    def get_lifetime(self):
        return self.get_expiration()
 
    ##
    # set the privileges
    #
    # @param privs either a comma-separated list of privileges of a Rights object

    def set_privileges(self, privs):
        if isinstance(privs, str):
            self.privileges = Rights(string = privs)
        else:
            self.privileges = privs        

    ##
    # return the privileges as a Rights object

    def get_privileges(self):
        if not self.privileges:
            self.decode()
        return self.privileges

    ##
    # determine whether the credential allows a particular operation to be
    # performed
    #
    # @param op_name string specifying name of operation ("lookup", "update", etc)

    def can_perform(self, op_name):
        rights = self.get_privileges()
        
        if not rights:
            return False

        return rights.can_perform(op_name)


    ##
    # Encode the attributes of the credential into an XML string    
    # This should be done immediately before signing the credential.    
    # 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 encode(self):
        # Create the XML document
        doc = Document()
        signed_cred = doc.createElement("signed-credential")

# 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)  
        
        # Fill in the <credential> bit        
        cred = doc.createElement("credential")
        cred.setAttribute("xml:id", self.get_refid())
        signed_cred.appendChild(cred)
        append_sub(doc, cred, "type", "privilege")
        append_sub(doc, cred, "serial", "8")
        append_sub(doc, cred, "owner_gid", self.gidCaller.save_to_string())
        append_sub(doc, cred, "owner_urn", self.gidCaller.get_urn())
        append_sub(doc, cred, "target_gid", self.gidObject.save_to_string())
        append_sub(doc, cred, "target_urn", self.gidObject.get_urn())
        append_sub(doc, cred, "uuid", "")
        if not self.expiration:
            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())
        privileges = doc.createElement("privileges")
        cred.appendChild(privileges)

        if self.privileges:
            rights = self.get_privileges()
            for right in rights.rights:
                priv = doc.createElement("privilege")
                append_sub(doc, priv, "name", right.kind)
                append_sub(doc, priv, "can_delegate", str(right.delegate).lower())
                privileges.appendChild(priv)

        # Add the parent credential if it exists
        if self.parent:
            sdoc = parseString(self.parent.get_xml())
            # 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 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:
#        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):
                    attr = parentRoot.attributes.item(attrIx)
                    # returns the old attribute of same name that was
                    # on the credential
                    # 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)
                        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.appendChild(p_cred)
            cred.appendChild(p)
        # done handling parent credential

        # Create the <signatures> tag
        signatures = doc.createElement("signatures")
        signed_cred.appendChild(signatures)

        # Add any parent signatures
        if self.parent:
            for cur_cred in self.get_credential_list()[1:]:
                sdoc = parseString(cur_cred.get_signature().get_xml())
                ele = doc.importNode(sdoc.getElementsByTagName("Signature")[0], True)
                signatures.appendChild(ele)
                
        # Get the finished product
        self.xml = doc.toxml()


    def save_to_random_tmp_file(self):       
        fp, filename = mkstemp(suffix='cred', text=True)
        fp = os.fdopen(fp, "w")
        self.save_to_file(filename, save_parents=True, filep=fp)
        return filename
    
    def save_to_file(self, filename, save_parents=True, filep=None):
        if not self.xml:
            self.encode()
        if filep:
            f = filep 
        else:
            f = open(filename, "w")
        f.write(self.xml)
        f.close()

    def save_to_string(self, save_parents=True):
        if not self.xml:
            self.encode()
        return self.xml

    def get_refid(self):
        if not self.refid:
            self.refid = 'ref0'
        return self.refid

    def set_refid(self, rid):
        self.refid = rid

    ##
    # Figure out what refids exist, and update this credential's id
    # so that it doesn't clobber the others.  Returns the refids of
    # the parents.
    
    def updateRefID(self):
        if not self.parent:
            self.set_refid('ref0')
            return []
        
        refs = []

        next_cred = self.parent
        while next_cred:
            refs.append(next_cred.get_refid())
            if next_cred.parent:
                next_cred = next_cred.parent
            else:
                next_cred = None

        
        # Find a unique refid for this credential
        rid = self.get_refid()
        while rid in refs:
            val = int(rid[3:])
            rid = "ref%d" % (val + 1)

        # Set the new refid
        self.set_refid(rid)

        # Return the set of parent credential ref ids
        return refs

    def get_xml(self):
        if not self.xml:
            self.encode()
        return self.xml

    ##
    # Sign the XML file created by encode()
    #
    # 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 sign(self):
        if not self.issuer_privkey:
            logger.warn("Cannot sign credential (no private key)")
            return
        if not self.issuer_gid:
            logger.warn("Cannot sign credential (no issuer gid)")
            return
        doc = parseString(self.get_xml())
        sigs = doc.getElementsByTagName("signatures")[0]

        # Create the signature template to be signed
        signature = Signature()
        signature.set_refid(self.get_refid())
        sdoc = parseString(signature.get_xml())        
        sig_ele = doc.importNode(sdoc.getElementsByTagName("Signature")[0], True)
        sigs.appendChild(sig_ele)

        self.xml = doc.toxml()


        # Split the issuer GID into multiple certificates if it's a chain
        chain = GID(filename=self.issuer_gid)
        gid_files = []
        while chain:
            gid_files.append(chain.save_to_random_tmp_file(False))
            if chain.get_parent():
                chain = chain.get_parent()
            else:
                chain = None


        # Call out to xmlsec1 to sign it
        ref = 'Sig_%s' % self.get_refid()
        filename = self.save_to_random_tmp_file()
        command='%s --sign --node-id "%s" --privkey-pem %s,%s %s' \
            % (self.xmlsec_path, ref, self.issuer_privkey, ",".join(gid_files), filename)
#        print 'command',command
        signed = os.popen(command).read()
        os.remove(filename)

        for gid_file in gid_files:
            os.remove(gid_file)

        self.xml = signed

        # This is no longer a legacy credential
        if self.legacy:
            self.legacy = None

        # Update signatures
        self.decode()       

        
    ##
    # Retrieve the attributes of the credential from the XML.
    # This is automatically called by the various get_* methods of
    # this class and should not need to be called explicitly.

    def decode(self):
        if not self.xml:
            return
        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:
            creds = signed_cred[0].getElementsByTagName("credential")
            signatures = signed_cred[0].getElementsByTagName("signatures")
            if len(signatures) > 0:
                sigs = signatures[0].getElementsByTagName("Signature")
        else:
            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.gidCaller = GID(string=getTextNode(cred, "owner_gid"))
        self.gidObject = GID(string=getTextNode(cred, "target_gid"))   


        # Process privileges
        privs = cred.getElementsByTagName("privileges")[0]
        rlist = Rights()
        for priv in privs.getElementsByTagName("privilege"):
            kind = getTextNode(priv, "name")
            deleg = str2bool(getTextNode(priv, "can_delegate"))
            if kind == '*':
                # 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 = determine_rights(type, self.gidObject.get_urn())
                for r in rl.rights:
                    r.delegate = deleg
                    rlist.add(r)
            else:
                rlist.add(Right(kind.strip(), deleg))
        self.set_privileges(rlist)


        # Is there a parent?
        parent = cred.getElementsByTagName("parent")
        if len(parent) > 0:
            parent_doc = parent[0].getElementsByTagName("credential")[0]
            parent_xml = parent_doc.toxml()
            self.parent = Credential(string=parent_xml)
            self.updateRefID()

        # Assign the signatures to the credentials
        for sig in sigs:
            Sig = Signature(string=sig.toxml())

            for cur_cred in self.get_credential_list():
                if cur_cred.get_refid() == Sig.get_refid():
                    cur_cred.set_signature(Sig)
                                    
            
    ##
    # Verify
    #   trusted_certs: A list of trusted GID filenames (not GID objects!) 
    #                  Chaining is not supported within the GIDs by xmlsec1.
    #
    #   trusted_certs_required: Should usually be true. Set False means an
    #                 empty list of trusted_certs would still let this method pass.
    #                 It just skips xmlsec1 verification et al. Only used by some utils
    #    
    # Verify that:
    # . All of the signatures are valid and that the issuers trace back
    #   to trusted roots (performed by xmlsec1)
    # . The XML matches the credential schema
    # . 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 privileges must be a subset of the parent credentials
    # . The privileges must have "can_delegate" set for each delegated privilege
    # . The target gid must be the same between child and parents
    # . The expiry time on the child must be no later than the parent
    # . The signer of the child must be the owner of the parent
    #
    # -- Verify does *NOT*
    # . ensure that an xmlrpc client's gid matches a credential gid, that
    #   must be done elsewhere
    #
    # @param trusted_certs: The certificates of trusted CA certificates
    def verify(self, trusted_certs=None, schema=None, trusted_certs_required=True):
        if not self.xml:
            self.decode()

        # validate against RelaxNG schema
        if HAVELXML and not self.legacy:
            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: %s (line %s)" % (self.get_summary_tostring(), error.message, error.line)
                    raise CredentialNotVerifiable(message)

        if trusted_certs_required and trusted_certs is None:
            trusted_certs = []

#        trusted_cert_objects = [GID(filename=f) for f in trusted_certs]
        trusted_cert_objects = []
        ok_trusted_certs = []
        # If caller explicitly passed in None that means skip cert chain validation.
        # Strange and not typical
        if trusted_certs is not None:
            for f in trusted_certs:
                try:
                    # Failures here include unreadable files
                    # or non PEM files
                    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))
            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()))

        # 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
        if trusted_certs is not None:
            # Verify the gids of this cred and of its parents
            for cur_cred in self.get_credential_list():
                cur_cred.get_gid_object().verify_chain(trusted_cert_objects)
                cur_cred.get_gid_caller().verify_chain(trusted_cert_objects)

        refs = []
        refs.append("Sig_%s" % self.get_refid())

        parentRefs = self.updateRefID()
        for ref in parentRefs:
            refs.append("Sig_%s" % ref)

        for ref in refs:
            # If caller explicitly passed in None that means skip xmlsec1 validation.
            # Strange and not typical
            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"):
                # xmlsec errors have a msg= which is the interesting bit.
                mstart = verified.find("msg=")
                msg = ""
                if mstart > -1 and len(verified) > 4:
                    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()))
        os.remove(filename)

        # Verify the parents (delegation)
        if self.parent:
            self.verify_parent(self.parent)

        # Make sure the issuer is the target's authority, and is
        # itself a valid GID
        self.verify_issuer(trusted_cert_objects)
        return True
Example #14
0
    def decode(self):
        if not self.xml:
            return
        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:
            creds = signed_cred[0].getElementsByTagName("credential")
            signatures = signed_cred[0].getElementsByTagName("signatures")
            if len(signatures) > 0:
                sigs = signatures[0].getElementsByTagName("Signature")
        else:
            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.gidCaller = GID(string=getTextNode(cred, "owner_gid"))
        self.gidObject = GID(string=getTextNode(cred, "target_gid"))

        # Process privileges
        privs = cred.getElementsByTagName("privileges")[0]
        rlist = Rights()
        for priv in privs.getElementsByTagName("privilege"):
            kind = getTextNode(priv, "name")
            deleg = str2bool(getTextNode(priv, "can_delegate"))
            if kind == '*':
                # 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 = determine_rights(type, self.gidObject.get_urn())
                for r in rl.rights:
                    r.delegate = deleg
                    rlist.add(r)
            else:
                rlist.add(Right(kind.strip(), deleg))
        self.set_privileges(rlist)

        # Is there a parent?
        parent = cred.getElementsByTagName("parent")
        if len(parent) > 0:
            parent_doc = parent[0].getElementsByTagName("credential")[0]
            parent_xml = parent_doc.toxml()
            self.parent = Credential(string=parent_xml)
            self.updateRefID()

        # Assign the signatures to the credentials
        for sig in sigs:
            Sig = Signature(string=sig.toxml())

            for cur_cred in self.get_credential_list():
                if cur_cred.get_refid() == Sig.get_refid():
                    cur_cred.set_signature(Sig)
Example #15
0
def get_serial_number(certificate):
    "Returns the serial number of given certificate"
    user_gid = GID(string=certificate)
    return user_gid.get_serial_number()
Example #16
0
def get_serial_number(certificate):
    "Returns the serial number of given certificate"
    user_gid = GID(string=certificate)
    return user_gid.get_serial_number()
Example #17
0
class Credential(object):

    ##
    # Create a Credential object
    #
    # @param create If true, create a blank x509 certificate
    # @param subject If subject!=None, create an x509 cert with the subject name
    # @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):
        self.gidCaller = None
        self.gidObject = None
        self.expiration = None
        self.privileges = None
        self.issuer_privkey = None
        self.issuer_gid = None
        self.issuer_pubkey = None
        self.parent = None
        self.signature = None
        self.xml = None
        self.refid = None
        self.legacy = None

        # 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)
            else:
                self.xml = str
                self.decode()

        # Find an xmlsec1 path
        self.xmlsec_path = ''
        paths = [
            '/usr/bin', '/usr/local/bin', '/bin', '/opt/bin', '/opt/local/bin'
        ]
        for path in paths:
            if os.path.isfile(path + '/' + 'xmlsec1'):
                self.xmlsec_path = path + '/' + 'xmlsec1'
                break
        if not self.xmlsec_path:
            logger.warn(
                "Could not locate binary for xmlsec1 - SFA will be unable to sign stuff !!"
            )

    def get_subject(self):
        if not self.gidObject:
            self.decode()
        return self.gidObject.get_subject()

    # sounds like this should be __repr__ instead ??
    def get_summary_tostring(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 %s rights on %s until %s ]" % (caller, obj, exp)

    def get_signature(self):
        if not self.signature:
            self.decode()
        return self.signature

    def set_signature(self, 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
    # @param gid GID of the issuing authority

    def set_issuer_keys(self, privkey, gid):
        self.issuer_privkey = privkey
        self.issuer_gid = gid

    ##
    # Set this credential's parent
    def set_parent(self, cred):
        self.parent = cred
        self.updateRefID()

    ##
    # set the GID of the caller
    #
    # @param gid GID object of the caller

    def set_gid_caller(self, gid):
        self.gidCaller = gid
        # gid origin caller is the caller's gid by default
        self.gidOriginCaller = gid

    ##
    # get the GID of the object

    def get_gid_caller(self):
        if not self.gidCaller:
            self.decode()
        return self.gidCaller

    ##
    # set the GID of the object
    #
    # @param gid GID object of the object

    def set_gid_object(self, gid):
        self.gidObject = gid

    ##
    # get the GID of the object

    def get_gid_object(self):
        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):
        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)
        else:
            logger.error("unexpected input type in Credential.set_expiration")

    ##
    # 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

    ##
    # For legacy sake
    def get_lifetime(self):
        return self.get_expiration()

    ##
    # set the privileges
    #
    # @param privs either a comma-separated list of privileges of a Rights object

    def set_privileges(self, privs):
        if isinstance(privs, str):
            self.privileges = Rights(string=privs)
        else:
            self.privileges = privs

    ##
    # return the privileges as a Rights object

    def get_privileges(self):
        if not self.privileges:
            self.decode()
        return self.privileges

    ##
    # determine whether the credential allows a particular operation to be
    # performed
    #
    # @param op_name string specifying name of operation ("lookup", "update", etc)

    def can_perform(self, op_name):
        rights = self.get_privileges()

        if not rights:
            return False

        return rights.can_perform(op_name)

    ##
    # Encode the attributes of the credential into an XML string
    # This should be done immediately before signing the credential.
    # 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 encode(self):
        # Create the XML document
        doc = Document()
        signed_cred = doc.createElement("signed-credential")

        # 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)

        # Fill in the <credential> bit
        cred = doc.createElement("credential")
        cred.setAttribute("xml:id", self.get_refid())
        signed_cred.appendChild(cred)
        append_sub(doc, cred, "type", "privilege")
        append_sub(doc, cred, "serial", "8")
        append_sub(doc, cred, "owner_gid", self.gidCaller.save_to_string())
        append_sub(doc, cred, "owner_urn", self.gidCaller.get_urn())
        append_sub(doc, cred, "target_gid", self.gidObject.save_to_string())
        append_sub(doc, cred, "target_urn", self.gidObject.get_urn())
        append_sub(doc, cred, "uuid", "")
        if not self.expiration:
            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())
        privileges = doc.createElement("privileges")
        cred.appendChild(privileges)

        if self.privileges:
            rights = self.get_privileges()
            for right in rights.rights:
                priv = doc.createElement("privilege")
                append_sub(doc, priv, "name", right.kind)
                append_sub(doc, priv, "can_delegate",
                           str(right.delegate).lower())
                privileges.appendChild(priv)

        # Add the parent credential if it exists
        if self.parent:
            sdoc = parseString(self.parent.get_xml())
            # 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 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:
            #        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):
                    attr = parentRoot.attributes.item(attrIx)
                    # returns the old attribute of same name that was
                    # on the credential
                    # 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)
                        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.appendChild(p_cred)
            cred.appendChild(p)
        # done handling parent credential

        # Create the <signatures> tag
        signatures = doc.createElement("signatures")
        signed_cred.appendChild(signatures)

        # Add any parent signatures
        if self.parent:
            for cur_cred in self.get_credential_list()[1:]:
                sdoc = parseString(cur_cred.get_signature().get_xml())
                ele = doc.importNode(
                    sdoc.getElementsByTagName("Signature")[0], True)
                signatures.appendChild(ele)

        # Get the finished product
        self.xml = doc.toxml()

    def save_to_random_tmp_file(self):
        fp, filename = mkstemp(suffix='cred', text=True)
        fp = os.fdopen(fp, "w")
        self.save_to_file(filename, save_parents=True, filep=fp)
        return filename

    def save_to_file(self, filename, save_parents=True, filep=None):
        if not self.xml:
            self.encode()
        if filep:
            f = filep
        else:
            f = open(filename, "w")
        f.write(self.xml)
        f.close()

    def save_to_string(self, save_parents=True):
        if not self.xml:
            self.encode()
        return self.xml

    def get_refid(self):
        if not self.refid:
            self.refid = 'ref0'
        return self.refid

    def set_refid(self, rid):
        self.refid = rid

    ##
    # Figure out what refids exist, and update this credential's id
    # so that it doesn't clobber the others.  Returns the refids of
    # the parents.

    def updateRefID(self):
        if not self.parent:
            self.set_refid('ref0')
            return []

        refs = []

        next_cred = self.parent
        while next_cred:
            refs.append(next_cred.get_refid())
            if next_cred.parent:
                next_cred = next_cred.parent
            else:
                next_cred = None

        # Find a unique refid for this credential
        rid = self.get_refid()
        while rid in refs:
            val = int(rid[3:])
            rid = "ref%d" % (val + 1)

        # Set the new refid
        self.set_refid(rid)

        # Return the set of parent credential ref ids
        return refs

    def get_xml(self):
        if not self.xml:
            self.encode()
        return self.xml

    ##
    # Sign the XML file created by encode()
    #
    # 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 sign(self):
        if not self.issuer_privkey:
            logger.warn("Cannot sign credential (no private key)")
            return
        if not self.issuer_gid:
            logger.warn("Cannot sign credential (no issuer gid)")
            return
        doc = parseString(self.get_xml())
        sigs = doc.getElementsByTagName("signatures")[0]

        # Create the signature template to be signed
        signature = Signature()
        signature.set_refid(self.get_refid())
        sdoc = parseString(signature.get_xml())
        sig_ele = doc.importNode(
            sdoc.getElementsByTagName("Signature")[0], True)
        sigs.appendChild(sig_ele)

        self.xml = doc.toxml()

        # Split the issuer GID into multiple certificates if it's a chain
        chain = GID(filename=self.issuer_gid)
        gid_files = []
        while chain:
            gid_files.append(chain.save_to_random_tmp_file(False))
            if chain.get_parent():
                chain = chain.get_parent()
            else:
                chain = None

        # Call out to xmlsec1 to sign it
        ref = 'Sig_%s' % self.get_refid()
        filename = self.save_to_random_tmp_file()
        command='%s --sign --node-id "%s" --privkey-pem %s,%s %s' \
            % (self.xmlsec_path, ref, self.issuer_privkey, ",".join(gid_files), filename)
        #        print 'command',command
        signed = os.popen(command).read()
        os.remove(filename)

        for gid_file in gid_files:
            os.remove(gid_file)

        self.xml = signed

        # This is no longer a legacy credential
        if self.legacy:
            self.legacy = None

        # Update signatures
        self.decode()

    ##
    # Retrieve the attributes of the credential from the XML.
    # This is automatically called by the various get_* methods of
    # this class and should not need to be called explicitly.

    def decode(self):
        if not self.xml:
            return
        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:
            creds = signed_cred[0].getElementsByTagName("credential")
            signatures = signed_cred[0].getElementsByTagName("signatures")
            if len(signatures) > 0:
                sigs = signatures[0].getElementsByTagName("Signature")
        else:
            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.gidCaller = GID(string=getTextNode(cred, "owner_gid"))
        self.gidObject = GID(string=getTextNode(cred, "target_gid"))

        # Process privileges
        privs = cred.getElementsByTagName("privileges")[0]
        rlist = Rights()
        for priv in privs.getElementsByTagName("privilege"):
            kind = getTextNode(priv, "name")
            deleg = str2bool(getTextNode(priv, "can_delegate"))
            if kind == '*':
                # 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 = determine_rights(type, self.gidObject.get_urn())
                for r in rl.rights:
                    r.delegate = deleg
                    rlist.add(r)
            else:
                rlist.add(Right(kind.strip(), deleg))
        self.set_privileges(rlist)

        # Is there a parent?
        parent = cred.getElementsByTagName("parent")
        if len(parent) > 0:
            parent_doc = parent[0].getElementsByTagName("credential")[0]
            parent_xml = parent_doc.toxml()
            self.parent = Credential(string=parent_xml)
            self.updateRefID()

        # Assign the signatures to the credentials
        for sig in sigs:
            Sig = Signature(string=sig.toxml())

            for cur_cred in self.get_credential_list():
                if cur_cred.get_refid() == Sig.get_refid():
                    cur_cred.set_signature(Sig)

    ##
    # Verify
    #   trusted_certs: A list of trusted GID filenames (not GID objects!)
    #                  Chaining is not supported within the GIDs by xmlsec1.
    #
    #   trusted_certs_required: Should usually be true. Set False means an
    #                 empty list of trusted_certs would still let this method pass.
    #                 It just skips xmlsec1 verification et al. Only used by some utils
    #
    # Verify that:
    # . All of the signatures are valid and that the issuers trace back
    #   to trusted roots (performed by xmlsec1)
    # . The XML matches the credential schema
    # . 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 privileges must be a subset of the parent credentials
    # . The privileges must have "can_delegate" set for each delegated privilege
    # . The target gid must be the same between child and parents
    # . The expiry time on the child must be no later than the parent
    # . The signer of the child must be the owner of the parent
    #
    # -- Verify does *NOT*
    # . ensure that an xmlrpc client's gid matches a credential gid, that
    #   must be done elsewhere
    #
    # @param trusted_certs: The certificates of trusted CA certificates
    def verify(self,
               trusted_certs=None,
               schema=None,
               trusted_certs_required=True,
               crl_path=None):
        if not self.xml:
            self.decode()

        # validate against RelaxNG schema
        if HAVELXML and not self.legacy:
            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: %s (line %s)" % (
                        self.get_summary_tostring(), error.message, error.line)
                    raise CredentialNotVerifiable(message)

        if trusted_certs_required and trusted_certs is None:
            trusted_certs = []

#        trusted_cert_objects = [GID(filename=f) for f in trusted_certs]
        trusted_cert_objects = []
        ok_trusted_certs = []
        # If caller explicitly passed in None that means skip cert chain validation.
        # Strange and not typical
        if trusted_certs is not None:
            for f in trusted_certs:
                try:
                    # Failures here include unreadable files
                    # or non PEM files
                    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))
            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()))

        # 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
        if trusted_certs is not None:
            # Verify the gids of this cred and of its parents
            for cur_cred in self.get_credential_list():
                cur_cred.get_gid_object().verify_chain(trusted_cert_objects,
                                                       crl_path)
                cur_cred.get_gid_caller().verify_chain(trusted_cert_objects,
                                                       crl_path)

        refs = []
        refs.append("Sig_%s" % self.get_refid())

        parentRefs = self.updateRefID()
        for ref in parentRefs:
            refs.append("Sig_%s" % ref)

        for ref in refs:
            # If caller explicitly passed in None that means skip xmlsec1 validation.
            # Strange and not typical
            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"):
                # xmlsec errors have a msg= which is the interesting bit.
                mstart = verified.find("msg=")
                msg = ""
                if mstart > -1 and len(verified) > 4:
                    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()))
        os.remove(filename)

        # Verify the parents (delegation)
        if self.parent:
            self.verify_parent(self.parent)

        # Make sure the issuer is the target's authority, and is
        # itself a valid GID
        self.verify_issuer(trusted_cert_objects)

        return True
Example #18
0
    def decode(self):
        if not self.xml:
            return
        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:
            creds = signed_cred[0].getElementsByTagName("credential")
            signatures = signed_cred[0].getElementsByTagName("signatures")
            if len(signatures) > 0:
                sigs = signatures[0].getElementsByTagName("Signature")
        else:
            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.gidCaller = GID(string=getTextNode(cred, "owner_gid"))
        self.gidObject = GID(string=getTextNode(cred, "target_gid"))   


        # Process privileges
        privs = cred.getElementsByTagName("privileges")[0]
        rlist = Rights()
        for priv in privs.getElementsByTagName("privilege"):
            kind = getTextNode(priv, "name")
            deleg = str2bool(getTextNode(priv, "can_delegate"))
            if kind == '*':
                # 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 = determine_rights(type, self.gidObject.get_urn())
                for r in rl.rights:
                    r.delegate = deleg
                    rlist.add(r)
            else:
                rlist.add(Right(kind.strip(), deleg))
        self.set_privileges(rlist)


        # Is there a parent?
        parent = cred.getElementsByTagName("parent")
        if len(parent) > 0:
            parent_doc = parent[0].getElementsByTagName("credential")[0]
            parent_xml = parent_doc.toxml()
            self.parent = Credential(string=parent_xml)
            self.updateRefID()

        # Assign the signatures to the credentials
        for sig in sigs:
            Sig = Signature(string=sig.toxml())

            for cur_cred in self.get_credential_list():
                if cur_cred.get_refid() == Sig.get_refid():
                    cur_cred.set_signature(Sig)
Example #19
0
                        uuidO = uuid.UUID(int=int(uuidarg))
                    except Exception, e:
                        try:
                            uuidO = uuid.UUID(fields=uuidarg)
                        except:
                            try:
                                uuidO = uuid.UUID(bytes=uuidarg)
                            except:
                                try:
                                    uuidO = uuid.UUID(bytes_le=uuidarg)
                                except:
                                    pass
        if uuidO is not None:
            uuidI = uuidO.int

    newgid = GID(create=True, subject=subject, uuid=uuidI, urn=urn, lifeDays=lifeDays, serial_number=serial_number)
    if email:
        newgid.set_email(email)
    
    if public_key is None:
        # create a new key pair
        keys = Keypair(create=True)
    else:
        # use the specified public key file
        keys = Keypair()
        keys.load_pubkey_from_file(public_key)
    newgid.set_pubkey(keys)
    newgid.set_is_ca(ca)

    if issuer_key and issuer_cert:
        # the given issuer will issue this cert