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