Exemplo n.º 1
0
    def invalid(self) -> Failure:
        failure = Failure(Component.IMA, ["validation"])
        if self.pcr != str(config.IMA_PCR):
            logger.warning("IMA entry PCR does not match %s. It was: %s",
                           config.IMA_PCR, self.pcr)
            failure.add_event("ima_pcr", {"message": "IMA PCR is not the configured one",
                                          "expected": str(config.IMA_PCR), "got": self.pcr}, True)

        # Ignore template hash for ToMToU errors
        if self.ima_template_hash == get_FF_HASH(self._ima_hash_alg):
            logger.warning("Skipped template_hash validation entry with FF_HASH")
            # By default ToMToU errors are not treated as a failure
            if config.getboolean("cloud_verifier", "tomtou_errors", fallback=False):
                failure.add_event("tomtou", "hash validation was skipped", True)
            return failure
        if self.ima_template_hash != self._ima_hash_alg.hash(self._bytes):
            failure.add_event("ima_hash",
                              {"message": "IMA template hash does not match the calculated hash.",
                               "expected": str(self.ima_template_hash), "got": str(self.mode.bytes())}, True)
            return failure
        if self._validator is None:
            failure.add_event("no_validator", "No validator specified", True)
            return failure

        failure.merge(self.mode.is_data_valid(self._validator))
        return failure
Exemplo n.º 2
0
 def __check_ima(agentAttestState, pcrval, ima_measurement_list, allowlist,
                 ima_keyrings, boot_aggregates, hash_alg):
     failure = Failure(Component.IMA)
     logger.info("Checking IMA measurement list on agent: %s",
                 agentAttestState.get_agent_id())
     _, ima_failure = ima.process_measurement_list(
         agentAttestState,
         ima_measurement_list.split('\n'),
         allowlist,
         pcrval=pcrval,
         ima_keyrings=ima_keyrings,
         boot_aggregates=boot_aggregates,
         hash_alg=hash_alg)
     failure.merge(ima_failure)
     if not failure:
         logger.debug("IMA measurement list of agent %s validated",
                      agentAttestState.get_agent_id())
     return failure
Exemplo n.º 3
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
Exemplo n.º 4
0
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
Exemplo n.º 5
0
    def check_pcrs(self, agentAttestState, tpm_policy, pcrs, data, virtual,
                   ima_measurement_list, allowlist, ima_keyrings,
                   mb_measurement_list, mb_refstate_str, hash_alg) -> Failure:
        failure = Failure(Component.PCR_VALIDATION)
        if isinstance(tpm_policy, str):
            tpm_policy = json.loads(tpm_policy)

        pcr_allowlist = tpm_policy.copy()

        if 'mask' in pcr_allowlist:
            del pcr_allowlist['mask']
        # convert all pcr num keys to integers
        pcr_allowlist = {int(k): v for k, v in list(pcr_allowlist.items())}

        mb_policy, mb_policy_name, mb_refstate_data = measured_boot.get_policy(
            mb_refstate_str)
        mb_pcrs_hashes, boot_aggregates, mb_measurement_data, mb_failure = self.parse_mb_bootlog(
            mb_measurement_list, hash_alg)
        failure.merge(mb_failure)

        pcrs_in_quote = set(
        )  # PCRs in quote that were already used for some kind of validation

        pcrs = AbstractTPM.__parse_pcrs(pcrs, virtual)
        pcr_nums = set(pcrs.keys())

        # Validate data PCR
        if config.TPM_DATA_PCR in pcr_nums and data is not None:
            expectedval = self.sim_extend(data, hash_alg=hash_alg)
            if expectedval != pcrs[config.TPM_DATA_PCR]:
                logger.error(
                    "%sPCR #%s: invalid bind data %s from quote does not match expected value %s",
                    ("", "v")[virtual], config.TPM_DATA_PCR,
                    pcrs[config.TPM_DATA_PCR], expectedval)
                failure.add_event(f"invalid_pcr_{config.TPM_DATA_PCR}", {
                    "got": pcrs[config.TPM_DATA_PCR],
                    "expected": expectedval
                }, True)
            pcrs_in_quote.add(config.TPM_DATA_PCR)
        else:
            logger.error(
                "Binding %sPCR #%s was not included in the quote, but is required",
                ("", "v")[virtual], config.TPM_DATA_PCR)
            failure.add_event(
                f"missing_pcr_{config.TPM_DATA_PCR}",
                f"Data PCR {config.TPM_DATA_PCR} is missing in quote, but is required",
                True)
        # Check for ima PCR
        if config.IMA_PCR in pcr_nums:
            if ima_measurement_list is None:
                logger.error(
                    "IMA PCR in policy, but no measurement list provided")
                failure.add_event(
                    f"unused_pcr_{config.IMA_PCR}",
                    "IMA PCR in policy, but no measurement list provided",
                    True)
            else:
                ima_failure = AbstractTPM.__check_ima(agentAttestState,
                                                      pcrs[config.IMA_PCR],
                                                      ima_measurement_list,
                                                      allowlist, ima_keyrings,
                                                      boot_aggregates,
                                                      hash_alg)
                failure.merge(ima_failure)

            pcrs_in_quote.add(config.IMA_PCR)

        # Collect mismatched measured boot PCRs as measured_boot failures
        mb_pcr_failure = Failure(Component.MEASURED_BOOT)
        # Handle measured boot PCRs only if the parsing worked
        if not mb_failure:
            for pcr_num in set(config.MEASUREDBOOT_PCRS) & pcr_nums:
                if mb_refstate_data:
                    if not mb_measurement_list:
                        logger.error(
                            "Measured Boot PCR %d in policy, but no measurement list provided",
                            pcr_num)
                        failure.add_event(
                            f"unused_pcr_{pcr_num}",
                            f"Measured Boot PCR {pcr_num} in policy, but no measurement list provided",
                            True)
                        continue

                    val_from_log_int = mb_pcrs_hashes.get(str(pcr_num), 0)
                    val_from_log_hex = hex(val_from_log_int)[2:]
                    val_from_log_hex_stripped = val_from_log_hex.lstrip('0')
                    pcrval_stripped = pcrs[pcr_num].lstrip('0')
                    if val_from_log_hex_stripped != pcrval_stripped:
                        logger.error(
                            "For PCR %d and hash %s the boot event log has value %r but the agent returned %r",
                            str(hash_alg), pcr_num, val_from_log_hex,
                            pcrs[pcr_num])
                        mb_pcr_failure.add_event(
                            f"invalid_pcr_{pcr_num}", {
                                "context":
                                "SHA256 boot event log PCR value does not match",
                                "got": pcrs[pcr_num],
                                "expected": val_from_log_hex
                            }, True)

                    if pcr_num in pcr_allowlist and pcrs[
                            pcr_num] not in pcr_allowlist[pcr_num]:
                        logger.error(
                            "%sPCR #%s: %s from quote does not match expected value %s",
                            ("", "v")[virtual], pcr_num, pcrs[pcr_num],
                            pcr_allowlist[pcr_num])
                        failure.add_event(
                            f"invalid_pcr_{pcr_num}", {
                                "context": "PCR value is not in allowlist",
                                "got": pcrs[pcr_num],
                                "expected": pcr_allowlist[pcr_num]
                            }, True)
                    pcrs_in_quote.add(pcr_num)
        failure.merge(mb_pcr_failure)

        # Check the remaining non validated PCRs
        for pcr_num in pcr_nums - pcrs_in_quote:
            if pcr_num not in list(pcr_allowlist.keys()):
                logger.warning(
                    "%sPCR #%s in quote not found in %stpm_policy, skipping.",
                    ("", "v")[virtual], pcr_num, ("", "v")[virtual])
                continue
            if pcrs[pcr_num] not in pcr_allowlist[pcr_num]:
                logger.error(
                    "%sPCR #%s: %s from quote does not match expected value %s",
                    ("", "v")[virtual], pcr_num, pcrs[pcr_num],
                    pcr_allowlist[pcr_num])
                failure.add_event(
                    f"invalid_pcr_{pcr_num}", {
                        "context": "PCR value is not in allowlist",
                        "got": pcrs[pcr_num],
                        "expected": pcr_allowlist[pcr_num]
                    }, True)

            pcrs_in_quote.add(pcr_num)

        missing = set(pcr_allowlist.keys()) - pcrs_in_quote
        if len(missing) > 0:
            logger.error("%sPCRs specified in policy not in quote: %s",
                         ("", "v")[virtual], missing)
            failure.add_event("missing_pcrs", {
                "context": "PCRs are missing in quote",
                "data": list(missing)
            }, True)

        if not mb_failure and mb_refstate_data:
            mb_policy_failure = measured_boot.evaluate_policy(
                mb_policy, mb_policy_name, mb_refstate_data,
                mb_measurement_data, pcrs_in_quote, ("", "v")[virtual],
                agentAttestState.get_agent_id())
            failure.merge(mb_policy_failure)

        return failure
Exemplo n.º 6
0
    def validate(self, digest: ast.Digest, path: ast.Name,
                 data: ast.Buffer) -> Failure:
        """Validate a single entry."""
        failure = Failure(Component.IMA, ["validation", "dm"])
        try:
            event = parse(data.data.decode("utf-8"), path.name)
            hash_alg = Hash(digest.algorithm)
            if digest.hash != hash_alg.hash(data.data):
                failure.add_event(
                    "invalid_data",
                    "hash in IMA log and of the actual data mismatch", True)

            match_key = self.policies["match_on"]

            if path.name == "dm_table_load":
                failure.merge(
                    self.validate_table_load(event, match_key, digest))
            elif path.name == "dm_device_resume":
                failure.merge(self.validate_device_resume(event, match_key))
            elif path.name == "dm_device_remove":
                failure.merge(self.validate_device_remove(event, match_key))
            elif path.name == "dm_device_rename":
                failure.merge(self.validate_device_rename(event, match_key))
            elif path.name == "dm_table_clear":
                failure.merge(self.validate_table_clear(event, match_key))
            elif path.name == "dm_target_update":
                failure.merge(self.validate_target_update(event, match_key))
            else:
                failure.add_event("invalid_event_type", {"got": path.name},
                                  True)

        except lark.exceptions.LarkError as e:
            failure.add_event("parsing_failed",
                              f"Could not construct valid entry: {e}", True)

        return failure