def test_valid_entry_construction(self): hash_alg = Hash.SHA1 for name, (expected_mode, data) in VALID_ENTRIES.items(): err = None try: entry = ast.Entry(data, AlwaysTrueValidator, ima_hash_alg=hash_alg, pcr_hash_alg=hash_alg) self.assertTrue( entry.pcr == "10", f"Expected pcr 10 for {name}. Got: {entry.pcr}") self.assertTrue( isinstance(entry.mode, expected_mode) # pylint: disable=isinstance-second-argument-not-valid-type ) self.assertTrue( entry.ima_template_hash == hash_alg.hash( entry.mode.bytes()), f"Constructed hash of {name} does not match template hash.\n Expected: {entry.ima_template_hash}.\n Got: {entry.mode.bytes()}", ) self.assertTrue(not entry.invalid(), f"Entry of {name} couldn't be validated.") except ast.ParserError as e: err = e if err: self.fail(f"Constructing entry {name} failed with: {err}")
def measure_list(file_path, position, ima_hash_alg, pcr_hash_alg, search_val=None): with open(file_path, encoding="utf-8") as f: lines = itertools.islice(f, position, None) runninghash = ast.get_START_HASH(pcr_hash_alg) if search_val is not None: search_val = codecs.decode(search_val.encode("utf-8"), "hex") for line in 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") position += 1 entry = ast.Entry(line, None, ima_hash_alg=ima_hash_alg, pcr_hash_alg=pcr_hash_alg) if search_val is None: val = codecs.encode(entry.pcr_template_hash, "hex").decode("utf8") tpm_instance.extendPCR(config.IMA_PCR, val, pcr_hash_alg) else: runninghash = pcr_hash_alg.hash(runninghash + entry.pcr_template_hash) if runninghash == search_val: return position if search_val is not None: raise Exception( "Unable to find current measurement list position, Resetting the TPM emulator may be neccesary" ) return position
def measure_list(file_path, position, ima_hash_alg, pcr_hash_alg, search_val=None): with open(file_path, encoding="utf-8") as f: lines = itertools.islice(f, position, None) runninghash = ast.get_START_HASH(pcr_hash_alg) if search_val is not None: search_val = codecs.decode(search_val.encode('utf-8'), 'hex') for line in lines: line = line.strip() position += 1 entry = ast.Entry(line, None, ima_hash_alg=ima_hash_alg, pcr_hash_alg=pcr_hash_alg) if search_val is None: val = codecs.encode(entry.pcr_template_hash, 'hex').decode("utf8") tpm_instance.extendPCR(config.IMA_PCR, val, pcr_hash_alg) else: runninghash = pcr_hash_alg.hash(runninghash + entry.pcr_template_hash) if runninghash == search_val: return position if search_val is not None: raise Exception( "Unable to find current measurement list position, Resetting the TPM emulator may be neccesary" ) return position
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 test_invalid_entries(self): for _, data in INVALID_ENTRIES.items(): with self.assertRaises(ast.ParserError): ast.Entry(data)
# pylint: disable=protected-access import unittest from keylime.ima import ast, ima_dm # Note that the tests depend also on ast parsing the raw data correctly. # Test dm_table_load events for all supported targets table_load_verity = ast.Entry( "10 fdcd389a7d084c7e1af8ed6917d080b1f0ee0625 ima-buf sha256:09e8a13203b10ce8d352aaafcdaf74986a6e2940e42c44c1a6603624135e1117 dm_table_load 646d5f76657273696f6e3d342e34352e303b6e616d653d746573742c757569643d43525950542d5645524954592d63373664303733343364336134396235616230313032356433623335346466352d746573742c6d616a6f723d3235332c6d696e6f723d302c6d696e6f725f636f756e743d312c6e756d5f746172676574733d313b7461726765745f696e6465783d302c7461726765745f626567696e3d302c7461726765745f6c656e3d3230343830382c7461726765745f6e616d653d7665726974792c7461726765745f76657273696f6e3d312e382e302c686173685f6661696c65643d562c7665726974795f76657273696f6e3d312c646174615f6465766963655f6e616d653d373a312c686173685f6465766963655f6e616d653d373a302c7665726974795f616c676f726974686d3d7368613235362c726f6f745f6469676573743d366561666665366238623031393930613165333937313236353734363865396237323263623634626139393432633664353836393438646131626434303936372c73616c743d643733386664396634323033663339376635613135353632633330323131393537303430636436373165666334363937313562663236383935363232656162632c69676e6f72655f7a65726f5f626c6f636b733d6e2c636865636b5f61745f6d6f73745f6f6e63653d6e3b" ) table_load_linear = ast.Entry( "10 6cde7a2687bc348d737f1a56f256abd962c96b4d ima-buf sha256:e4a5f19a9f827c1442a76f52c91b149abbef7d327c9a20afa3768a8ac7362334 dm_table_load 646d5f76657273696f6e3d342e34352e303b6e616d653d6964656e746974792c757569643d746573742c6d616a6f723d3235332c6d696e6f723d302c6d696e6f725f636f756e743d312c6e756d5f746172676574733d313b7461726765745f696e6465783d302c7461726765745f626567696e3d302c7461726765745f6c656e3d343236383033322c7461726765745f6e616d653d6c696e6561722c7461726765745f76657273696f6e3d312e342e302c6465766963655f6e616d653d3235343a322c73746172743d303b" ) table_load_snapshot = ast.Entry( "10 e63f7fc6ac88ff78154d2841c23a6205dad7cca4 ima-buf sha256:97fb89def8c8938f90b5b79441654beb84663f64974e76956d950f9e93da7cb2 dm_table_load 646d5f76657273696f6e3d342e34352e303b6e616d653d736e6170332c757569643d746573742d736e61702c6d616a6f723d3235332c6d696e6f723d312c6d696e6f725f636f756e743d312c6e756d5f746172676574733d313b7461726765745f696e6465783d302c7461726765745f626567696e3d302c7461726765745f6c656e3d31303438353736302c7461726765745f6e616d653d736e617073686f742c7461726765745f76657273696f6e3d312e31362e302c736e61705f6f726967696e5f6e616d653d3235333a302c736e61705f636f775f6e616d653d3235323a302c736e61705f76616c69643d792c736e61705f6d657267655f6661696c65643d6e2c736e617073686f745f6f766572666c6f7765643d6e3b" ) table_load_integrity = ast.Entry( "10 15c72d3162ffbdda697c2a0b318545fc2604455d ima-buf sha256:823424c152324a18fbbf788788f1ad97eb89863f0e86fbe63aa7df88a6e4fb12 dm_table_load 646d5f76657273696f6e3d342e34352e303b6e616d653d746573742d696e746567726974792c757569643d43525950542d494e544547524954592d746573742d696e746567726974792c6d616a6f723d3235332c6d696e6f723d312c6d696e6f725f636f756e743d312c6e756d5f746172676574733d313b7461726765745f696e6465783d302c7461726765745f626567696e3d302c7461726765745f6c656e3d3230313432342c7461726765745f6e616d653d696e746567726974792c7461726765745f76657273696f6e3d312e31302e302c6465765f6e616d653d373a302c73746172743d302c7461675f73697a653d342c6d6f64653d4a2c726563616c63756c6174653d6e2c616c6c6f775f64697363617264733d6e2c6669785f70616464696e673d792c6669785f686d61633d792c6c65676163795f726563616c63756c6174653d6e2c6a6f75726e616c5f736563746f72733d313538342c696e7465726c656176655f736563746f72733d33323736382c6275666665725f736563746f72733d3132383b" ) table_load_crypt = ast.Entry( "10 a55d85d4a6059b44960938b3893f521479e7421e ima-buf sha256:19d0d1eed3d4d1127519e22d63978a1fb58cbab368e13e6204e3c12f64dd9f51 dm_table_load 646d5f76657273696f6e3d342e34352e303b6e616d653d746573742c757569643d43525950542d4c554b53322d38613536343438333362613734633134616534326661313330666138386163612d746573742c6d616a6f723d3235332c6d696e6f723d322c6d696e6f725f636f756e743d312c6e756d5f746172676574733d313b7461726765745f696e6465783d302c7461726765745f626567696e3d302c7461726765745f6c656e3d3137323034302c7461726765745f6e616d653d63727970742c7461726765745f76657273696f6e3d312e32332e302c616c6c6f775f64697363617264733d6e2c73616d655f6370755f63727970743d6e2c7375626d69745f66726f6d5f63727970745f637075733d6e2c6e6f5f726561645f776f726b71756575653d6e2c6e6f5f77726974655f776f726b71756575653d6e2c69765f6c617267655f736563746f72733d6e2c6369706865725f737472696e673d6165732d7874732d706c61696e36342c6b65795f73697a653d36342c6b65795f70617274733d312c6b65795f65787472615f73697a653d302c6b65795f6d61635f73697a653d303b" ) table_load_cache = ast.Entry( "10 daa949b2e19a473922b5b27b05df9a8425842d22 ima-buf sha256:cbcb9a0db9280f4a19d8e06a9825f1effc6db3e0fa0b2c72096ce8b7a534e6df dm_table_load 646d5f76657273696f6e3d342e34352e303b6e616d653d63616368652c757569643d63616368652c6d616a6f723d3235332c6d696e6f723d342c6d696e6f725f636f756e743d312c6e756d5f746172676574733d313b7461726765745f696e6465783d302c7461726765745f626567696e3d302c7461726765745f6c656e3d323034383030302c7461726765745f6e616d653d63616368652c7461726765745f76657273696f6e3d322e322e302c6d657461646174615f6d6f64653d72772c63616368655f6d657461646174615f6465766963653d373a322c63616368655f6465766963653d373a332c63616368655f6f726967696e5f6465766963653d373a342c77726974657468726f7567683d6e2c77726974656261636b3d792c706173737468726f7567683d6e2c6d65746164617461323d6e2c6e6f5f646973636172645f70617373646f776e3d6e3b" ) table_load_mirror = ast.Entry( "10 5e686ad192b519cb316ad191def2403f90b96b16 ima-buf sha256:7548978b7d86b776adf00ce11659cc0142b719be8d4b83e3b53ff6d090f73812 dm_table_load 646d5f76657273696f6e3d342e34352e303b6e616d653d6d6972726f722c757569643d746573742d6d6972726f722c6d616a6f723d3235332c6d696e6f723d352c6d696e6f725f636f756e743d312c6e756d5f746172676574733d313b7461726765745f696e6465783d302c7461726765745f626567696e3d302c7461726765745f6c656e3d323034383030302c7461726765745f6e616d653d6d6972726f722c7461726765745f76657273696f6e3d312e31342e302c6e725f6d6972726f72733d322c6d6972726f725f6465766963655f303d373a332c6d6972726f725f6465766963655f305f7374617475733d412c6d6972726f725f6465766963655f313d373a322c6d6972726f725f6465766963655f315f7374617475733d412c68616e646c655f6572726f72733d792c6b6565705f6c6f673d6e2c6c6f675f747970655f7374617475733d3b" ) # All other dm events they from the same device as the tabel_load_verity event