Ejemplo n.º 1
0
def process_quote_response(agent, json_response, agentAttestState) -> Failure:
    """Validates the response from the Cloud agent.

    This method invokes an Registrar Server call to register, and then check the quote.
    """
    failure = Failure(Component.QUOTE_VALIDATION)
    received_public_key = None
    quote = None
    # in case of failure in response content do not continue
    try:
        received_public_key = json_response.get("pubkey", None)
        quote = json_response["quote"]

        ima_measurement_list = json_response.get("ima_measurement_list", None)
        ima_measurement_list_entry = json_response.get(
            "ima_measurement_list_entry", 0)
        mb_measurement_list = json_response.get("mb_measurement_list", None)
        boottime = json_response.get("boottime", 0)

        logger.debug("received quote:      %s", quote)
        logger.debug("for nonce:           %s", agent['nonce'])
        logger.debug("received public key: %s", received_public_key)
        logger.debug("received ima_measurement_list    %s",
                     (ima_measurement_list is not None))
        logger.debug("received ima_measurement_list_entry: %d",
                     ima_measurement_list_entry)
        logger.debug("received boottime: %s", boottime)
        logger.debug("received boot log    %s",
                     (mb_measurement_list is not None))
    except Exception as e:
        failure.add_event("invalid_data", {
            "message": "parsing agents get quote respone failed",
            "data": e
        }, False)
        return failure

    # TODO: Are those separate failures?
    if not isinstance(ima_measurement_list_entry, int):
        raise Exception(
            "ima_measurement_list_entry parameter must be an integer")

    if not isinstance(boottime, int):
        raise Exception("boottime parameter must be an integer")

    # if no public key provided, then ensure we have cached it
    if received_public_key is None:
        if agent.get('public_key', "") == "" or agent.get(
                'b64_encrypted_V', "") == "":
            logger.error(
                "agent did not provide public key and no key or encrypted_v was cached at CV"
            )
            failure.add_event(
                "no_pubkey",
                "agent did not provide public key and no key or encrypted_v was cached at CV",
                False)
            return failure
        agent['provide_V'] = False
        received_public_key = agent['public_key']

    hash_alg = json_response.get('hash_alg')
    enc_alg = json_response.get('enc_alg')
    sign_alg = json_response.get('sign_alg')

    # Update chosen tpm and algorithms
    agent['hash_alg'] = hash_alg
    agent['enc_alg'] = enc_alg
    agent['sign_alg'] = sign_alg

    # Ensure hash_alg is in accept_tpm_hash_alg list
    if not algorithms.is_accepted(hash_alg, agent['accept_tpm_hash_algs'])\
            or not algorithms.Hash.is_recognized(hash_alg):
        logger.error("TPM Quote is using an unaccepted hash algorithm: %s",
                     hash_alg)
        failure.add_event(
            "invalid_hash_alg", {
                "message":
                f"TPM Quote is using an unaccepted hash algorithm: {hash_alg}",
                "data": hash_alg
            }, False)
        return failure

    # Ensure enc_alg is in accept_tpm_encryption_algs list
    if not algorithms.is_accepted(enc_alg,
                                  agent['accept_tpm_encryption_algs']):
        logger.error(
            "TPM Quote is using an unaccepted encryption algorithm: %s",
            enc_alg)
        failure.add_event(
            "invalid_enc_alg", {
                "message":
                f"TPM Quote is using an unaccepted encryption algorithm: {enc_alg}",
                "data": enc_alg
            }, False)
        return failure

    # Ensure sign_alg is in accept_tpm_encryption_algs list
    if not algorithms.is_accepted(sign_alg, agent['accept_tpm_signing_algs']):
        logger.error("TPM Quote is using an unaccepted signing algorithm: %s",
                     sign_alg)
        failure.add_event(
            "invalid_sign_alg", {
                "message":
                f"TPM Quote is using an unaccepted signing algorithm: {sign_alg}",
                "data": {sign_alg}
            }, False)
        return failure

    if ima_measurement_list_entry == 0:
        agentAttestState.reset_ima_attestation()
    elif ima_measurement_list_entry != agentAttestState.get_next_ima_ml_entry(
    ):
        # If we requested a particular entry number then the agent must return either
        # starting at 0 (handled above) or with the requested number.
        logger.error(
            "Agent did not respond with requested next IMA measurement list entry %s but started at %s",
            agentAttestState.get_next_ima_ml_entry(),
            ima_measurement_list_entry)
        failure.add_event(
            "invalid_ima_entry_nb", {
                "message":
                "Agent did not respond with requested next IMA measurement list entry",
                "got": ima_measurement_list_entry,
                "expected": agentAttestState.get_next_ima_ml_entry()
            }, False)
    elif not agentAttestState.is_expected_boottime(boottime):
        # agent sent a list not starting at 0 and provided a boottime that doesn't
        # match the expected boottime, so it must have been rebooted; we would fail
        # attestation this time so we retry with a full attestation next time.
        agentAttestState.reset_ima_attestation()
        return failure

    agentAttestState.set_boottime(boottime)

    ima_keyrings = agentAttestState.get_ima_keyrings()
    tenant_keyring = file_signatures.ImaKeyring.from_string(
        agent['ima_sign_verification_keys'])
    ima_keyrings.set_tenant_keyring(tenant_keyring)

    quote_validation_failure = get_tpm_instance().check_quote(
        agentAttestState,
        agent['nonce'],
        received_public_key,
        quote,
        agent['ak_tpm'],
        agent['tpm_policy'],
        ima_measurement_list,
        agent['allowlist'],
        algorithms.Hash(hash_alg),
        ima_keyrings,
        mb_measurement_list,
        agent['mb_refstate'],
        compressed=(agent['supported_version'] == "1.0")
    )  # TODO: change this to always False after initial update
    failure.merge(quote_validation_failure)

    if not failure:
        # set a flag so that we know that the agent was verified once.
        # we only issue notifications for agents that were at some point good
        agent['first_verified'] = True

        # has public key changed? if so, clear out b64_encrypted_V, it is no longer valid
        if received_public_key != agent.get('public_key', ""):
            agent['public_key'] = received_public_key
            agent['b64_encrypted_V'] = ""
            agent['provide_V'] = True

    # ok we're done
    return failure
Ejemplo n.º 2
0
def process_quote_response(agent, json_response):
    """Validates the response from the Cloud agent.

    This method invokes an Registrar Server call to register, and then check the quote.
    """
    received_public_key = None
    quote = None

    # in case of failure in response content do not continue
    try:
        received_public_key = json_response.get("pubkey", None)
        quote = json_response["quote"]

        ima_measurement_list = json_response.get("ima_measurement_list", None)

        logger.debug("received quote:      %s" % quote)
        logger.debug("for nonce:           %s" % agent['nonce'])
        logger.debug("received public key: %s" % received_public_key)
        logger.debug("received ima_measurement_list    %s" %
                     (ima_measurement_list is not None))
    except Exception:
        return None

    # if no public key provided, then ensure we have cached it
    if received_public_key is None:
        if agent.get('public_key', "") == "" or agent.get(
                'b64_encrypted_V', "") == "":
            logger.error(
                "agent did not provide public key and no key or encrypted_v was cached at CV"
            )
            return False
        agent['provide_V'] = False
        received_public_key = agent['public_key']

    if agent.get('registrar_keys', "") == "":
        registrar_client.init_client_tls('cloud_verifier')
        registrar_keys = registrar_client.getKeys(
            config.get("cloud_verifier", "registrar_ip"),
            config.get("cloud_verifier", "registrar_port"), agent['agent_id'])
        if registrar_keys is None:
            logger.warning("AIK not found in registrar, quote not validated")
            return False
        agent['registrar_keys'] = registrar_keys

    tpm_version = json_response.get('tpm_version')
    tpm = tpm_obj.getTPM(need_hw_tpm=False, tpm_version=tpm_version)
    hash_alg = json_response.get('hash_alg')
    enc_alg = json_response.get('enc_alg')
    sign_alg = json_response.get('sign_alg')

    # Update chosen tpm and algorithms
    agent['tpm_version'] = tpm_version
    agent['hash_alg'] = hash_alg
    agent['enc_alg'] = enc_alg
    agent['sign_alg'] = sign_alg

    # Ensure hash_alg is in accept_tpm_hash_alg list
    if not algorithms.is_accepted(hash_alg, agent['accept_tpm_hash_algs']):
        raise Exception("TPM Quote is using an unaccepted hash algorithm: %s" %
                        hash_alg)

    # Ensure enc_alg is in accept_tpm_encryption_algs list
    if not algorithms.is_accepted(enc_alg,
                                  agent['accept_tpm_encryption_algs']):
        raise Exception(
            "TPM Quote is using an unaccepted encryption algorithm: %s" %
            enc_alg)

    # Ensure sign_alg is in accept_tpm_encryption_algs list
    if not algorithms.is_accepted(sign_alg, agent['accept_tpm_signing_algs']):
        raise Exception(
            "TPM Quote is using an unaccepted signing algorithm: %s" %
            sign_alg)

    if tpm.is_deep_quote(quote):
        validQuote = tpm.check_deep_quote(
            agent['agent_id'], agent['nonce'], received_public_key, quote,
            agent['registrar_keys']['aik'],
            agent['registrar_keys']['provider_keys']['aik'],
            agent['vtpm_policy'], agent['tpm_policy'], ima_measurement_list,
            agent['allowlist'])
    else:
        validQuote = tpm.check_quote(agent['agent_id'], agent['nonce'],
                                     received_public_key, quote,
                                     agent['registrar_keys']['aik'],
                                     agent['tpm_policy'], ima_measurement_list,
                                     agent['allowlist'], hash_alg)
    if not validQuote:
        return False

    # set a flag so that we know that the agent was verified once.
    # we only issue notifications for agents that were at some point good
    agent['first_verified'] = True

    # has public key changed? if so, clear out b64_encrypted_V, it is no longer valid
    if received_public_key != agent.get('public_key', ""):
        agent['public_key'] = received_public_key
        agent['b64_encrypted_V'] = ""
        agent['provide_V'] = True

    # ok we're done
    return validQuote
Ejemplo n.º 3
0
    def do_quote(self):
        """ Perform TPM quote by GET towards Agent

        Raises:
            UserError: Connection handler
        """
        self.nonce = TPM_Utilities.random_password(20)

        numtries = 0
        response = None
        # Note: We need a specific retry handler (perhaps in common), no point having localised unless we have too.
        while True:
            try:
                params = '/quotes/identity?nonce=%s' % (self.nonce)
                cloudagent_base_url = f'{self.agent_ip}:{self.agent_port}'
                do_quote = RequestsClient(cloudagent_base_url, tls_enabled=False)
                response = do_quote.get(
                    params,
                    cert=self.cert
                )
                response_body = response.json()

            except Exception as e:
                if response.status_code in (503, 504):
                    numtries += 1
                    maxr = config.getint('tenant', 'max_retries')
                    if numtries >= maxr:
                        logger.error("Tenant cannot establish connection to agent on %s with port %s", self.agent_ip, self.agent_port)
                        sys.exit()
                    retry = config.getfloat('tenant', 'retry_interval')
                    logger.info("Tenant connection to agent at %s refused %s/%s times, trying again in %s seconds...",
                        self.agent_ip, numtries, maxr, retry)
                    time.sleep(retry)
                    continue

                raise e
            break

        try:
            if response is not None and response.status_code != 200:
                raise UserError(
                    "Status command response: %d Unexpected response from Cloud Agent." % response.status)

            if "results" not in response_body:
                raise UserError(
                    "Error: unexpected http response body from Cloud Agent: %s" % str(response.status))

            quote = response_body["results"]["quote"]
            logger.debug("Agent_quote received quote: %s", quote)

            public_key = response_body["results"]["pubkey"]
            logger.debug("Agent_quote received public key: %s", public_key)

            # Ensure hash_alg is in accept_tpm_hash_algs list
            hash_alg = response_body["results"]["hash_alg"]
            logger.debug("Agent_quote received hash algorithm: %s", hash_alg)
            if not algorithms.is_accepted(hash_alg, config.get('tenant', 'accept_tpm_hash_algs').split(',')):
                raise UserError(
                    "TPM Quote is using an unaccepted hash algorithm: %s" % hash_alg)

            # Ensure enc_alg is in accept_tpm_encryption_algs list
            enc_alg = response_body["results"]["enc_alg"]
            logger.debug("Agent_quote received encryption algorithm: %s", enc_alg)
            if not algorithms.is_accepted(enc_alg, config.get('tenant', 'accept_tpm_encryption_algs').split(',')):
                raise UserError(
                    "TPM Quote is using an unaccepted encryption algorithm: %s" % enc_alg)

            # Ensure sign_alg is in accept_tpm_encryption_algs list
            sign_alg = response_body["results"]["sign_alg"]
            logger.debug("Agent_quote received signing algorithm: %s", sign_alg)
            if not algorithms.is_accepted(sign_alg, config.get('tenant', 'accept_tpm_signing_algs').split(',')):
                raise UserError(
                    "TPM Quote is using an unaccepted signing algorithm: %s" % sign_alg)

            if not self.validate_tpm_quote(public_key, quote, hash_alg):
                raise UserError(
                    "TPM Quote from cloud agent is invalid for nonce: %s" % self.nonce)

            logger.info("Quote from %s validated", self.agent_ip)

            # encrypt U with the public key
            encrypted_U = crypto.rsa_encrypt(
                crypto.rsa_import_pubkey(public_key), self.U)

            b64_encrypted_u = base64.b64encode(encrypted_U)
            logger.debug("b64_encrypted_u: %s", b64_encrypted_u.decode('utf-8'))
            data = {
                'encrypted_key': b64_encrypted_u,
                'auth_tag': self.auth_tag
            }

            if self.payload is not None:
                data['payload'] = self.payload

            u_json_message = json.dumps(data)

            # post encrypted U back to CloudAgent
            params = '/keys/ukey'
            cloudagent_base_url = (
                f'{self.agent_ip}:{self.agent_port}'
            )

            post_ukey = RequestsClient(cloudagent_base_url, tls_enabled=False)
            response = post_ukey.post(
                params,
                data=u_json_message
            )

            if response.status_code == 503:
                logger.error("Cannot connect to Agent at %s with Port %s. Connection refused.", self.agent_ip, self.agent_port)
                sys.exit()
            elif response.status_code == 504:
                logger.error("Verifier at %s with Port %s timed out.", self.verifier_ip, self.verifier_port)
                sys.exit()

            if response.status_code != 200:
                keylime_logging.log_http_response(
                    logger, logging.ERROR, response_body)
                raise UserError(
                    "Posting of Encrypted U to the Cloud Agent failed with response code %d" % response.status)
        except Exception as e:
            self.do_cvstop()
            raise e