예제 #1
0
def validate_agent_data(agent_data):
    if agent_data is None:
        return False, None

    # validate that the allowlist is proper JSON
    lists = json.loads(agent_data['allowlist'])

    # Validate exlude list contains valid regular expressions
    is_valid, _, err_msg = validators.valid_exclude_list(lists.get('exclude'))
    if not is_valid:
        err_msg += " Exclude list regex is misformatted. Please correct the issue and try again."

    return is_valid, err_msg
예제 #2
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
예제 #3
0
 def test_invalid(self):
     """Check an invalid exclude list."""
     value = validators.valid_exclude_list([r"a["])
     self.assertEqual(
         value, (False, None, "Invalid regex: unterminated character set.")
     )
예제 #4
0
 def test_multi(self):
     """Check a multiple elements exclude list."""
     value = validators.valid_exclude_list([r"a.*", r"b.*"])
     self.assertTrue(value[0])
     self.assertEqual(value[1].pattern, r"(a.*)|(b.*)")
     self.assertEqual(value[2], None)
예제 #5
0
 def test_single(self):
     """Check a single exclude list element."""
     value = validators.valid_exclude_list([r"a.*"])
     self.assertTrue(value[0])
     self.assertEqual(value[1].pattern, r"(a.*)")
     self.assertEqual(value[2], None)
예제 #6
0
 def test_none(self):
     """Check that the empty list is valid."""
     self.assertEqual(validators.valid_exclude_list(None), (True, None, None))