コード例 #1
0
def main(argv=sys.argv):
    parser = argparse.ArgumentParser()
    parser.add_argument('-a',
                        '--hash_algs',
                        nargs='*',
                        default=['sha1'],
                        help='PCR banks hash algorithms')
    parser.add_argument('-i',
                        '--ima-hash-alg',
                        default='sha1',
                        help='Set hash algorithm that is used in IMA log')
    parser.add_argument('-f',
                        '--ima-log',
                        default=config.IMA_ML,
                        help='path to the IMA log')
    args = parser.parse_args(argv[1:])

    if not tpm_instance.is_emulator():
        raise Exception("This stub should only be used with a TPM emulator")

    ima_hash_alg = algorithms.Hash(args.ima_hash_alg)
    position = {}
    for pcr_hash_alg in args.hash_algs:
        pcr_hash_alg = algorithms.Hash(pcr_hash_alg)
        position[pcr_hash_alg] = 0

    for pcr_hash_alg in position.keys():
        pcr_val = tpm_instance.readPCR(config.IMA_PCR, pcr_hash_alg)
        if codecs.decode(pcr_val.encode('utf-8'),
                         'hex') != ima_ast.get_START_HASH(pcr_hash_alg):
            print(
                f"Warning: IMA PCR is not empty for hash algorithm {pcr_hash_alg}, "
                "trying to find the last updated file in the measurement list..."
            )
            position[pcr_hash_alg] = measure_list(args.ima_log,
                                                  position[pcr_hash_alg],
                                                  ima_hash_alg, pcr_hash_alg,
                                                  pcr_val)

    print(f"Monitoring {args.ima_log}")
    poll_object = select.poll()
    fd_object = open(args.ima_log, encoding="utf-8")
    number = fd_object.fileno()
    poll_object.register(fd_object, select.POLLIN | select.POLLPRI)

    try:
        while True:
            results = poll_object.poll()
            for result in results:
                if result[0] != number:
                    continue
                for pcr_hash_alg, pos in position.items():
                    position[pcr_hash_alg] = measure_list(
                        args.ima_log, pos, ima_hash_alg, pcr_hash_alg)

                time.sleep(0.2)
    except (SystemExit, KeyboardInterrupt):
        fd_object.close()
        sys.exit(1)
コード例 #2
0
def main(argv=sys.argv):  # pylint: disable=dangerous-default-value
    parser = argparse.ArgumentParser()
    parser.add_argument("-a",
                        "--hash_algs",
                        nargs="*",
                        default=["sha1"],
                        help="PCR banks hash algorithms")
    parser.add_argument("-i",
                        "--ima-hash-alg",
                        default="sha1",
                        help="Set hash algorithm that is used in IMA log")
    parser.add_argument("-f",
                        "--ima-log",
                        default=config.IMA_ML,
                        help="path to the IMA log")
    args = parser.parse_args(argv[1:])

    if not tpm_instance.is_emulator():
        raise Exception("This stub should only be used with a TPM emulator")

    ima_hash_alg = algorithms.Hash(args.ima_hash_alg)
    position = {}
    for pcr_hash_alg in args.hash_algs:
        pcr_hash_alg = algorithms.Hash(pcr_hash_alg)
        position[pcr_hash_alg] = 0

    for pcr_hash_alg in dict(position):
        pcr_val = tpm_instance.readPCR(config.IMA_PCR, pcr_hash_alg)
        if codecs.decode(pcr_val.encode("utf-8"),
                         "hex") != ast.get_START_HASH(pcr_hash_alg):
            print(
                f"Warning: IMA PCR is not empty for hash algorithm {pcr_hash_alg}, "
                "trying to find the last updated file in the measurement list..."
            )
            position[pcr_hash_alg] = measure_list(args.ima_log,
                                                  position[pcr_hash_alg],
                                                  ima_hash_alg, pcr_hash_alg,
                                                  pcr_val)

    print(f"Monitoring {args.ima_log}")
    poll_object = select.poll()
    with open(args.ima_log, encoding="utf-8") as fd_object:
        number = fd_object.fileno()
        poll_object.register(fd_object, select.POLLIN | select.POLLPRI)

        try:
            while True:
                results = poll_object.poll()
                for result in results:
                    if result[0] != number:
                        continue
                    for pcr_hash_alg, pos in position.items():
                        position[pcr_hash_alg] = measure_list(
                            args.ima_log, pos, ima_hash_alg, pcr_hash_alg)

                    time.sleep(0.2)
        except (SystemExit, KeyboardInterrupt):
            sys.exit(1)
コード例 #3
0
    def test_040_agent_quotes_integrity_get(self):
        """Test agent's GET /quotes/integrity Interface"""
        global public_key

        self.assertIsNotNone(
            aik_tpm, "Required value not set.  Previous step may have failed?")

        nonce = tpm_abstract.TPM_Utilities.random_password(20)
        mask = self.tpm_policy["mask"]
        partial = "1"
        if public_key is None:
            partial = "0"

        test_040_agent_quotes_integrity_get = RequestsClient(
            tenant_templ.agent_base_url,
            tls_enabled=True,
            ignore_hostname=True)
        response = test_040_agent_quotes_integrity_get.get(
            f"/v{self.api_version}/quotes/integrity?nonce={nonce}&mask={mask}&partial={partial}",
            cert=tenant_templ.agent_cert,
            verify=False,  # TODO: use agent certificate
        )

        self.assertEqual(response.status_code, 200,
                         "Non-successful Agent Integrity Get return code!")
        json_response = response.json()

        # Ensure response is well-formed
        self.assertIn("results", json_response, "Malformed response body!")
        self.assertIn("quote", json_response["results"],
                      "Malformed response body!")
        if public_key is None:
            self.assertIn("pubkey", json_response["results"],
                          "Malformed response body!")
            public_key = json_response["results"]["pubkey"]
        self.assertIn("hash_alg", json_response["results"],
                      "Malformed response body!")

        quote = json_response["results"]["quote"]
        hash_alg = algorithms.Hash(json_response["results"]["hash_alg"])

        agentAttestState = cloud_verifier_common.get_AgentAttestStates(
        ).get_by_agent_id(tenant_templ.agent_uuid)

        failure = tpm_instance.check_quote(agentAttestState,
                                           nonce,
                                           public_key,
                                           quote,
                                           aik_tpm,
                                           self.tpm_policy,
                                           hash_alg=hash_alg)
        self.assertTrue(not failure)
コード例 #4
0
    def test_022_agent_quotes_identity_get(self):
        """Test agent's GET /quotes/identity Interface"""
        self.assertIsNotNone(
            aik_tpm, "Required value not set.  Previous step may have failed?")

        nonce = tpm_abstract.TPM_Utilities.random_password(20)

        numretries = config.getint("tenant", "max_retries")
        while numretries >= 0:
            test_022_agent_quotes_identity_get = RequestsClient(
                tenant_templ.agent_base_url,
                tls_enabled=True,
                ignore_hostname=True)
            response = test_022_agent_quotes_identity_get.get(
                f"/v{self.api_version}/quotes/identity?nonce={nonce}",
                data=None,
                cert=tenant_templ.agent_cert,
                verify=False,  # TODO: use agent certificate
            )

            if response.status_code == 200:
                break
            numretries -= 1
            time.sleep(config.getint("tenant", "retry_interval"))
        self.assertEqual(response.status_code, 200,
                         "Non-successful Agent identity return code!")
        json_response = response.json()

        # Ensure response is well-formed
        self.assertIn("results", json_response, "Malformed response body!")
        self.assertIn("quote", json_response["results"],
                      "Malformed response body!")
        self.assertIn("pubkey", json_response["results"],
                      "Malformed response body!")

        agentAttestState = cloud_verifier_common.get_AgentAttestStates(
        ).get_by_agent_id(tenant_templ.agent_uuid)

        # Check the quote identity
        failure = tpm_instance.check_quote(
            agentAttestState,
            nonce,
            json_response["results"]["pubkey"],
            json_response["results"]["quote"],
            aik_tpm,
            hash_alg=algorithms.Hash(json_response["results"]["hash_alg"]),
        )
        self.assertTrue(not failure, "Invalid quote!")
コード例 #5
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
コード例 #6
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