コード例 #1
0
def _validate_ima_sig(exclude_regex, ima_keyrings, allowlist, digest: ima_ast.Digest, path: ima_ast.Name,
                      signature: ima_ast.Signature) -> Failure:
    failure = Failure(Component.IMA, ["validator", "ima-sig"])
    valid_signature = False
    if ima_keyrings and signature:

        if exclude_regex is not None and exclude_regex.match(path.name):
            logger.debug(f"IMA: ignoring excluded path {path.name}")
            return failure

        if not ima_keyrings.integrity_digsig_verify(signature.data, digest.hash, digest.algorithm):
            logger.warning(f"signature for file {path.name} is not valid")
            failure.add_event("invalid_signature", f"signature for file {path.name} is not valid", True)
            return failure

        valid_signature = True
        logger.debug("signature for file %s is good" % path)

    # If there is also an allowlist verify the file against that but only do this if:
    # - we did not evaluate the signature (valid_siganture = False)
    # - the signature is valid and the file is also in the allowlist
    if allowlist is not None and allowlist.get('hashes') is not None and \
        ((allowlist['hashes'].get(path.name, None) is not None and valid_signature) or not valid_signature):
        # We use the normal ima_ng validator to validate hash
        return _validate_ima_ng(exclude_regex, allowlist, digest, path)

    # If we don't have a allowlist and don't have a keyring we just ignore the validation.
    if ima_keyrings is None:
        return failure

    if not valid_signature:
        failure.add_event("invalid_signature", f"signature for file {path.name} could not be validated", True)
    return failure
コード例 #2
0
ファイル: ima.py プロジェクト: mpeters/keylime
def _validate_ima_ng(exclude_regex, allowlist, digest: ast.Digest, path: ast.Name, hash_types="hashes") -> Failure:
    failure = Failure(Component.IMA, ["validation", "ima-ng"])
    if allowlist is not None:
        if exclude_regex is not None and exclude_regex.match(path.name):
            logger.debug("IMA: ignoring excluded path %s", path)
            return failure

        accept_list = allowlist[hash_types].get(path.name, None)
        if accept_list is None:
            logger.warning("File not found in allowlist: %s", path.name)
            failure.add_event("not_in_allowlist", f"File not found in allowlist: {path.name}", True)
            return failure

        if codecs.encode(digest.hash, "hex").decode("utf-8") not in accept_list:
            logger.warning(
                "Hashes for file %s don't match %s not in %s",
                path.name,
                codecs.encode(digest.hash, "hex").decode("utf-8"),
                accept_list,
            )
            failure.add_event(
                "allowlist_hash",
                {
                    "message": "Hash not in allowlist found",
                    "got": codecs.encode(digest.hash, "hex").decode("utf-8"),
                    "expected": accept_list,
                },
                True,
            )
            return failure

    return failure
コード例 #3
0
def evaluate_policy(mb_policy, mb_policy_name, mb_refstate_data,
                    mb_measurement_data, pcrsInQuote, pcrPrefix,
                    agent_id) -> Failure:
    failure = Failure(Component.MEASURED_BOOT)
    missing = list(set(config.MEASUREDBOOT_PCRS).difference(pcrsInQuote))
    if len(missing) > 0:
        logger.error("%sPCRs specified for measured boot not in quote: %s",
                     pcrPrefix, missing)
        failure.add_event("missing_pcrs", {
            "context": "PCRs are missing in quote",
            "data": missing
        }, True)
    try:
        reason = mb_policy.evaluate(mb_refstate_data, mb_measurement_data)
    except Exception as exn:
        reason = f"policy evaluation failed: {str(exn)}"
    if reason:
        logger.error(
            "Boot attestation failed for agent %s, policy %s, refstate=%s, reason=%s",
            agent_id,
            mb_policy_name,
            json.dumps(mb_refstate_data),
            reason,
        )
        failure.add_event(
            f"policy_{mb_policy_name}",
            {
                "context": "Boot attestation failed",
                "policy": mb_policy_name,
                "refstate": mb_refstate_data,
                "reason": reason,
            },
            True,
        )
    return failure
コード例 #4
0
ファイル: ima_dm.py プロジェクト: mbestavros/keylime
    def validate_device_remove(self, event: "RemoveEvent",
                               match_key) -> Failure:
        failure = Failure(Component.IMA,
                          ["validation", "dm", "dm_device_remove"])

        # TODO: check if we can always use the active table
        device_key = getattr(event.device_active_metadata, match_key)
        device_state = self.devices.get(device_key, None)
        if device_state is None:
            failure.add_event("remove_before_table_load",
                              "Remove event before table was loaded", True)
            return failure

        policy = self.policies["rules"][device_state.policy_name]
        if not policy["device_remove"]["allow_removal"]:
            failure.add_event(
                "device_removed",
                f"Device {device_key} was remove, but that is not allowed",
                True)
            device_state.valid_state = False
            return failure

        # Remove device completely
        del self.devices[device_key]
        return failure
コード例 #5
0
ファイル: ast.py プロジェクト: stefanberger/keylime
 def get_validator(self, class_type: typing.Type['Mode']) -> Callable[..., Failure]:
     validator = self.functions.get(class_type, None)
     if validator is None:
         logger.warning("No validator was implemented for: %s. Using always false validator!",
                        class_type)
         failure = Failure(Component.IMA, ["validation"])
         failure.add_event("no_validator", f"No validator was implemented for: {class_type} . Using always false validator!", True)
         return lambda *_: failure
     return validator
コード例 #6
0
ファイル: ast.py プロジェクト: stefanberger/keylime
    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
コード例 #7
0
ファイル: ima_dm.py プロジェクト: mbestavros/keylime
    def validate_target_update(self, event: "UpdateEvent",
                               match_key) -> Failure:
        failure = Failure(Component.IMA,
                          ["validation", "dm", "dm_target_update"])

        device_key = getattr(event.device_metadata, match_key)
        device_state = self.devices.get(device_key, None)
        if device_state is None:
            failure.add_event("update_before_table_load",
                              "Update event before table was loaded", True)
            return failure

        policy = self.policies["rules"][device_state.policy_name]
        target_data = policy["table_load"]["targets"][
            event.target.target_index]

        # Validate the target data again
        self.validate_target_table(event.target, target_data, failure)

        return failure
コード例 #8
0
ファイル: ima_dm.py プロジェクト: mbestavros/keylime
    def validate_table_clear(self, event: "ClearEvent", match_key) -> Failure:
        failure = Failure(Component.IMA,
                          ["validation", "dm", "dm_table_clear"])

        device_key = getattr(event.device_metadata, match_key)
        device_state = self.devices.get(device_key, None)
        if device_state is None:
            failure.add_event("clear_before_table_load",
                              "Clear event before table was loaded", True)
            return failure

        policy = self.policies["rules"][device_state.policy_name]
        if not policy["allow_clear"]:
            failure.add_event(
                "table_cleared",
                f"Table for device {device_key} was cleared, but that is not allowed",
                True)
            device_state.valid_state = False
            return failure

        return failure
コード例 #9
0
ファイル: ima_dm.py プロジェクト: mbestavros/keylime
    def validate_target_table(target, reference_values,
                              failure: Failure) -> None:
        """
        Validates a target table entry against reference_values in a policy.
        If a failure occurs it is added to the failure object.
        """
        valid = True
        for key in reference_values.keys():
            data = target
            # Non default target arguments are stored in target_attributes
            if key not in [
                    "target_index", "target_begin", "target_len",
                    "target_name", "target_version"
            ]:
                data = target.target_attributes
            try:
                if not _check_attr(getattr(data, key), reference_values[key]):
                    failure.add_event(
                        "target_data_mismatch",
                        {
                            "got": getattr(data, key),
                            "expected": reference_values[key],
                            "context": key
                        },
                        True,
                    )
                    valid = False
            except AttributeError as e:
                failure.add_event(
                    "target_attribute_not_found",
                    {"context": f"Key {key} not found on target: {e}"}, True)

        if not valid:
            failure.add_event("target_data_invalid",
                              "target data was not valid", True)
コード例 #10
0
ファイル: ima_dm.py プロジェクト: mbestavros/keylime
    def invalid(self) -> Failure:
        """
        Check if the devices are in a consistent state
        """
        failure = Failure(Component.IMA, ["validation", "dm"])
        used_policies = set()
        # Check if the devices are in a valid state
        for device_name, device in self.devices.items():
            if not device.valid_state:
                failure.add_event("device_invalid_state",
                                  {"context": device_name}, True)
            else:
                # Only add polices that are on valid states
                used_policies.add(device.policy_name)

        # Check for required active policies
        for policy_name, policy in self.policies["rules"].items():
            if policy["required"] and policy_name not in used_policies:
                failure.add_event(
                    "required_policy_not_in_use",
                    f"policy {policy_name} required but not used", True)
        return failure
コード例 #11
0
ファイル: ima_dm.py プロジェクト: mbestavros/keylime
    def validate_device_rename(self, event: "RenameEvent",
                               match_key) -> Failure:
        failure = Failure(Component.IMA,
                          ["validation", "dm", "dm_device_rename"])

        device_key = getattr(event.device_metadata, match_key)
        device_state = self.devices.get(device_key, None)
        if device_state is None:
            failure.add_event("rename_before_table_load",
                              "Rename event before table was loaded", True)
            return failure

        policy = self.policies["rules"][device_state.policy_name]

        # Only check if the name changed
        if event.device_metadata.name != event.new_name:
            if not _check_attr(event.new_name,
                               policy["device_rename"]["valid_name"]):
                failure.add_event(
                    "new_name_invalid",
                    {
                        "message": "New name is invalid",
                        "got": event.new_name,
                        "expected": policy["device_rename"]["valid_name"],
                    },
                    True,
                )
                device_state.valid_state = False
            elif match_key == "name":
                self.devices[event.new_name] = self.devices.pop(device_key)

        # Only check if uuid changed
        if event.device_metadata.uuid != event.new_uuid:
            if not _check_attr(event.new_uuid,
                               policy["device_rename"]["valid_uuid"]):
                failure.add_event(
                    "new_uuid_invalid",
                    {
                        "message": "New name is invalid",
                        "got": event.new_uuid,
                        "expected": policy["device_rename"]["valid_uuid"],
                    },
                    True,
                )
                device_state.valid_state = False
            elif match_key == "uuid":
                self.devices[event.new_name] = self.devices.pop(device_key)

        return failure
コード例 #12
0
ファイル: ima_dm.py プロジェクト: mbestavros/keylime
    def validate_device_resume(self, event: "ResumeEvent",
                               match_key) -> Failure:
        failure = Failure(Component.IMA,
                          ["validation", "dm", "dm_device_resume"])

        device_key = getattr(event.device_metadata, match_key)
        if device_key not in self.devices:
            failure.add_event("resume_before_table_load",
                              "Resume event before table was loaded", True)
            return failure

        device_state = self.devices[device_key]

        # We only expect one resume event
        if device_state.active:
            failure.add_event("already_active", "table is already active",
                              True)
            device_state.valid_state = False
            return failure

        # Check if the table hash is consistent
        if device_state.active_table_hash != event.active_table_hash:
            failure.add_event(
                "active_table_mismatch",
                {
                    "got": event.active_table_hash,
                    "expected": device_state.active_table_hash,
                    "context": "resume does not match the table",
                },
                True,
            )
            device_state.valid_state = False
            return failure

        # TODO: Check also current capacity.

        # Mark now device as active and in a valid state
        device_state.active = True
        device_state.valid_state = True
        return failure
コード例 #13
0
ファイル: ima_dm.py プロジェクト: mbestavros/keylime
    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
コード例 #14
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
コード例 #15
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
コード例 #16
0
ファイル: tpm_abstract.py プロジェクト: stefanberger/keylime
    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
コード例 #17
0
ファイル: ima_dm.py プロジェクト: mbestavros/keylime
    def validate_table_load(self, event: "LoadEvent", match_key,
                            digest) -> Failure:
        failure = Failure(Component.IMA, ["validation", "dm", "dm_table_load"])

        device_key = getattr(event.device_metadata, match_key)

        if device_key in self.devices and not self.devices[
                device_key].allow_multiple_loads:
            failure.add_event(
                "multiple_table_loads",
                f"Multiple table load entries for device: {device_key}", True)
            return failure

        # Find matching policy
        used_policy_name = None
        used_policy = None
        for policy_name, policy in self.policies["rules"].items():
            if re.fullmatch(policy["table_load"][match_key], device_key):
                used_policy = policy
                used_policy_name = policy_name
                break

        if used_policy_name is None:
            failure.add_event("no_matching_policy", "No policy found", True)
            return failure

        # Validate device metadata
        for entry in [
                "name", "uuid", "major", "minor", "minor_count", "num_targets"
        ]:
            if not _check_attr(getattr(event.device_metadata, entry),
                               used_policy["table_load"][entry]):
                failure.add_event(
                    "invalid_entry",
                    {
                        "got": getattr(event.device_metadata, entry),
                        "expected": used_policy["table_load"][entry],
                        "context": entry,
                    },
                    True,
                )
                return failure

        # Check "num_targets"
        # Note that we get actually could get multiple lines, but this does not happen for our use cases so we
        # treat it as a failure.
        if event.device_metadata.num_targets != len(
                event.targets) or used_policy["table_load"][entry] != len(
                    event.targets):
            failure.add_event("num_targets_mismatch",
                              "lengths are not consistent", True)
            return failure

        # Validate targets
        for actual_target, should_values in zip(
                event.targets, used_policy["table_load"]["targets"]):
            self.validate_target_table(actual_target, should_values, failure)

        # Add device to validator
        self.devices[device_key] = DeviceState(
            active=False,
            valid_state=not used_policy["device_resume_required"],
            # Device normally has a resume to be in a fully validated state
            active_table_hash=digest,
            inactive_table_hash=None,
            policy_name=used_policy_name,
            allow_multiple_loads=used_policy["table_load"]
            ["allow_multiple_loads"],
            num_targets=event.device_metadata.num_targets,
        )
        return failure