Example #1
0
 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}")
Example #2
0
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
Example #4
0
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
Example #5
0
 def test_invalid_entries(self):
     for _, data in INVALID_ENTRIES.items():
         with self.assertRaises(ast.ParserError):
             ast.Entry(data)
Example #6
0
# 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