def main(argv=sys.argv): parser = argparse.ArgumentParser() parser.add_argument('-a', '--hash_algs', nargs='*', default=['sha1'], help='PCR banks hash algorithms') parser.add_argument('-i', '--ima-hash-alg', default='sha1', help='Set hash algorithm that is used in IMA log') parser.add_argument('-f', '--ima-log', default=config.IMA_ML, help='path to the IMA log') args = parser.parse_args(argv[1:]) if not tpm_instance.is_emulator(): raise Exception("This stub should only be used with a TPM emulator") ima_hash_alg = algorithms.Hash(args.ima_hash_alg) position = {} for pcr_hash_alg in args.hash_algs: pcr_hash_alg = algorithms.Hash(pcr_hash_alg) position[pcr_hash_alg] = 0 for pcr_hash_alg in position.keys(): pcr_val = tpm_instance.readPCR(config.IMA_PCR, pcr_hash_alg) if codecs.decode(pcr_val.encode('utf-8'), 'hex') != ima_ast.get_START_HASH(pcr_hash_alg): print( f"Warning: IMA PCR is not empty for hash algorithm {pcr_hash_alg}, " "trying to find the last updated file in the measurement list..." ) position[pcr_hash_alg] = measure_list(args.ima_log, position[pcr_hash_alg], ima_hash_alg, pcr_hash_alg, pcr_val) print(f"Monitoring {args.ima_log}") poll_object = select.poll() fd_object = open(args.ima_log, encoding="utf-8") number = fd_object.fileno() poll_object.register(fd_object, select.POLLIN | select.POLLPRI) try: while True: results = poll_object.poll() for result in results: if result[0] != number: continue for pcr_hash_alg, pos in position.items(): position[pcr_hash_alg] = measure_list( args.ima_log, pos, ima_hash_alg, pcr_hash_alg) time.sleep(0.2) except (SystemExit, KeyboardInterrupt): fd_object.close() sys.exit(1)
def main(argv=sys.argv): # pylint: disable=dangerous-default-value parser = argparse.ArgumentParser() parser.add_argument("-a", "--hash_algs", nargs="*", default=["sha1"], help="PCR banks hash algorithms") parser.add_argument("-i", "--ima-hash-alg", default="sha1", help="Set hash algorithm that is used in IMA log") parser.add_argument("-f", "--ima-log", default=config.IMA_ML, help="path to the IMA log") args = parser.parse_args(argv[1:]) if not tpm_instance.is_emulator(): raise Exception("This stub should only be used with a TPM emulator") ima_hash_alg = algorithms.Hash(args.ima_hash_alg) position = {} for pcr_hash_alg in args.hash_algs: pcr_hash_alg = algorithms.Hash(pcr_hash_alg) position[pcr_hash_alg] = 0 for pcr_hash_alg in dict(position): pcr_val = tpm_instance.readPCR(config.IMA_PCR, pcr_hash_alg) if codecs.decode(pcr_val.encode("utf-8"), "hex") != ast.get_START_HASH(pcr_hash_alg): print( f"Warning: IMA PCR is not empty for hash algorithm {pcr_hash_alg}, " "trying to find the last updated file in the measurement list..." ) position[pcr_hash_alg] = measure_list(args.ima_log, position[pcr_hash_alg], ima_hash_alg, pcr_hash_alg, pcr_val) print(f"Monitoring {args.ima_log}") poll_object = select.poll() with open(args.ima_log, encoding="utf-8") as fd_object: number = fd_object.fileno() poll_object.register(fd_object, select.POLLIN | select.POLLPRI) try: while True: results = poll_object.poll() for result in results: if result[0] != number: continue for pcr_hash_alg, pos in position.items(): position[pcr_hash_alg] = measure_list( args.ima_log, pos, ima_hash_alg, pcr_hash_alg) time.sleep(0.2) except (SystemExit, KeyboardInterrupt): sys.exit(1)
def test_040_agent_quotes_integrity_get(self): """Test agent's GET /quotes/integrity Interface""" global public_key self.assertIsNotNone( aik_tpm, "Required value not set. Previous step may have failed?") nonce = tpm_abstract.TPM_Utilities.random_password(20) mask = self.tpm_policy["mask"] partial = "1" if public_key is None: partial = "0" test_040_agent_quotes_integrity_get = RequestsClient( tenant_templ.agent_base_url, tls_enabled=True, ignore_hostname=True) response = test_040_agent_quotes_integrity_get.get( f"/v{self.api_version}/quotes/integrity?nonce={nonce}&mask={mask}&partial={partial}", cert=tenant_templ.agent_cert, verify=False, # TODO: use agent certificate ) self.assertEqual(response.status_code, 200, "Non-successful Agent Integrity Get return code!") json_response = response.json() # Ensure response is well-formed self.assertIn("results", json_response, "Malformed response body!") self.assertIn("quote", json_response["results"], "Malformed response body!") if public_key is None: self.assertIn("pubkey", json_response["results"], "Malformed response body!") public_key = json_response["results"]["pubkey"] self.assertIn("hash_alg", json_response["results"], "Malformed response body!") quote = json_response["results"]["quote"] hash_alg = algorithms.Hash(json_response["results"]["hash_alg"]) agentAttestState = cloud_verifier_common.get_AgentAttestStates( ).get_by_agent_id(tenant_templ.agent_uuid) failure = tpm_instance.check_quote(agentAttestState, nonce, public_key, quote, aik_tpm, self.tpm_policy, hash_alg=hash_alg) self.assertTrue(not failure)
def test_022_agent_quotes_identity_get(self): """Test agent's GET /quotes/identity Interface""" self.assertIsNotNone( aik_tpm, "Required value not set. Previous step may have failed?") nonce = tpm_abstract.TPM_Utilities.random_password(20) numretries = config.getint("tenant", "max_retries") while numretries >= 0: test_022_agent_quotes_identity_get = RequestsClient( tenant_templ.agent_base_url, tls_enabled=True, ignore_hostname=True) response = test_022_agent_quotes_identity_get.get( f"/v{self.api_version}/quotes/identity?nonce={nonce}", data=None, cert=tenant_templ.agent_cert, verify=False, # TODO: use agent certificate ) if response.status_code == 200: break numretries -= 1 time.sleep(config.getint("tenant", "retry_interval")) self.assertEqual(response.status_code, 200, "Non-successful Agent identity return code!") json_response = response.json() # Ensure response is well-formed self.assertIn("results", json_response, "Malformed response body!") self.assertIn("quote", json_response["results"], "Malformed response body!") self.assertIn("pubkey", json_response["results"], "Malformed response body!") agentAttestState = cloud_verifier_common.get_AgentAttestStates( ).get_by_agent_id(tenant_templ.agent_uuid) # Check the quote identity failure = tpm_instance.check_quote( agentAttestState, nonce, json_response["results"]["pubkey"], json_response["results"]["quote"], aik_tpm, hash_alg=algorithms.Hash(json_response["results"]["hash_alg"]), ) self.assertTrue(not failure, "Invalid quote!")
def _process_measurement_list(agentAttestState, lines, hash_alg, lists=None, m2w=None, pcrval=None, ima_keyrings=None, boot_aggregates=None): failure = Failure(Component.IMA) running_hash = agentAttestState.get_pcr_state(config.IMA_PCR, hash_alg) found_pcr = pcrval is None errors = {} pcrval_bytes = b"" if pcrval is not None: pcrval_bytes = codecs.decode(pcrval.encode("utf-8"), "hex") if lists is not None: if isinstance(lists, str): lists = json.loads(lists) allow_list = lists["allowlist"] exclude_list = lists["exclude"] else: allow_list = None exclude_list = None ima_log_hash_alg = algorithms.Hash.SHA1 if allow_list is not None: try: ima_log_hash_alg = algorithms.Hash( allow_list["ima"]["log_hash_alg"]) except ValueError: logger.warning( "Specified IMA log hash algorithm %s is not a valid algorithm! Defaulting to SHA1.", allow_list["ima"]["log_hash_alg"], ) if boot_aggregates and allow_list: if "boot_aggregate" not in allow_list["hashes"]: allow_list["hashes"]["boot_aggregate"] = [] for alg in boot_aggregates.keys(): for val in boot_aggregates[alg]: if val not in allow_list["hashes"]["boot_aggregate"]: allow_list["hashes"]["boot_aggregate"].append(val) is_valid, compiled_regex, err_msg = validators.valid_exclude_list( exclude_list) if not is_valid: # This should not happen as the exclude list has already been validated # by the verifier before acceping it. This is a safety net just in case. err_msg += " Exclude list will be ignored." logger.error(err_msg) # Setup device mapper validation dm_validator = None if allow_list is not None: dm_policy = allow_list["ima"]["dm_policy"] if dm_policy is not None: dm_validator = ima_dm.DmIMAValidator(dm_policy) dm_state = agentAttestState.get_ima_dm_state() # Only load state when using incremental attestation if agentAttestState.get_next_ima_ml_entry() != 0: dm_validator.state_load(dm_state) ima_validator = ast.Validator({ ast.ImaSig: functools.partial(_validate_ima_sig, compiled_regex, ima_keyrings, allow_list), ast.ImaNg: functools.partial(_validate_ima_ng, compiled_regex, allow_list), ast.Ima: functools.partial(_validate_ima_ng, compiled_regex, allow_list), ast.ImaBuf: functools.partial(_validate_ima_buf, compiled_regex, allow_list, ima_keyrings, dm_validator), }) # Iterative attestation may send us no log [len(lines) == 1]; compare last know PCR 10 state # against current PCR state. # Since IMA log append and PCR extend is not atomic, we may get a quote that does not yet take # into account the next appended measurement's [len(lines) == 2] PCR extension. if not found_pcr and len(lines) <= 2: found_pcr = running_hash == pcrval_bytes for linenum, line in enumerate(lines): # remove only the newline character, as there can be the space # as the delimiter character followed by an empty field at the # end line = line.strip("\n") if line == "": continue try: entry = ast.Entry(line, ima_validator, ima_hash_alg=ima_log_hash_alg, pcr_hash_alg=hash_alg) # update hash running_hash = hash_alg.hash(running_hash + entry.pcr_template_hash) validation_failure = entry.invalid() if validation_failure: failure.merge(validation_failure) errors[type(entry.mode)] = errors.get(type(entry.mode), 0) + 1 if not found_pcr: # End of list should equal pcr value found_pcr = running_hash == pcrval_bytes if found_pcr: logger.debug("Found match at linenum %s", linenum + 1) # We always want to have the very last line for the attestation, so # we keep the previous runninghash, which is not the last one! agentAttestState.update_ima_attestation( int(entry.pcr), running_hash, linenum + 1) if dm_validator: agentAttestState.set_ima_dm_state( dm_validator.state_dump()) # Keep old functionality for writing the parsed files with hashes into a file if m2w is not None and (type(entry.mode) in [ast.Ima, ast.ImaNg, ast.ImaSig]): hash_value = codecs.encode(entry.mode.digest.bytes, "hex") path = entry.mode.path.name m2w.write(f"{hash_value} {path}\n") except ast.ParserError: failure.add_event( "entry", f"Line was not parsable into a valid IMA entry: {line}", True, ["parser"]) logger.error("Line was not parsable into a valid IMA entry: %s", line) # check PCR value has been found if not found_pcr: logger.error("IMA measurement list does not match TPM PCR %s", pcrval) failure.add_event( "pcr_mismatch", f"IMA measurement list does not match TPM PCR {pcrval}", True) # Check if any validators failed if sum(errors.values()) > 0: error_msg = "IMA ERRORS: Some entries couldn't be validated. Number of failures in modes: " error_msg += ", ".join( [f"{k.__name__ } {v}" for k, v in errors.items()]) logger.error("%s.", error_msg) return codecs.encode(running_hash, "hex").decode("utf-8"), failure
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