示例#1
0
    def do_POST(self):
        """This method handles the POST requests to add agents to the Registrar Server.

        Currently, only agents resources are available for POSTing, i.e. /agents. All other POST uri's
        will return errors. POST requests require an an agent_id identifying the agent to add, and json
        block sent in the body with 2 entries: ek and aik.
        """
        session = SessionManager().make_session(engine)
        rest_params = web_util.get_restful_params(self.path)
        if rest_params is None:
            web_util.echo_json_response(
                self, 405, "Not Implemented: Use /agents/ interface")
            return

        if not rest_params["api_version"]:
            web_util.echo_json_response(self, 400, "API Version not supported")
            return

        if "agents" not in rest_params:
            web_util.echo_json_response(self, 400, "uri not supported")
            logger.warning(
                'POST agent returning 400 response. uri not supported: %s',
                self.path)
            return

        agent_id = rest_params["agents"]

        if agent_id is None:
            web_util.echo_json_response(self, 400, "agent id not found in uri")
            logger.warning(
                'POST agent returning 400 response. agent id not found in uri %s',
                self.path)
            return

        # If the agent ID is not valid (wrong set of characters), just
        # do nothing.
        if not validators.valid_agent_id(agent_id):
            web_util.echo_json_response(self, 400, "agent id not valid")
            logger.error("POST received an invalid agent ID: %s", agent_id)
            return

        try:
            content_length = int(self.headers.get('Content-Length', 0))
            if content_length == 0:
                web_util.echo_json_response(
                    self, 400, "Expected non zero content length")
                logger.warning(
                    'POST for %s returning 400 response. Expected non zero content length.',
                    agent_id)
                return

            post_body = self.rfile.read(content_length)
            json_body = json.loads(post_body)

            ekcert = json_body['ekcert']
            aik_tpm = json_body['aik_tpm']

            initialize_tpm = tpm()

            if ekcert is None or ekcert == 'emulator':
                logger.warning('Agent %s did not submit an ekcert' % agent_id)
                ek_tpm = json_body['ek_tpm']
            else:
                if 'ek_tpm' in json_body:
                    # This would mean the agent submitted both a non-None ekcert, *and*
                    #  an ek_tpm... We can deal with it by just ignoring the ek_tpm they sent
                    logger.warning(
                        'Overriding ek_tpm for agent %s from ekcert' %
                        agent_id)
                # If there's an EKCert, we just overwrite their ek_tpm
                # Note, we don't validate the EKCert here, other than the implicit
                #  "is it a valid x509 cert" check. So it's still untrusted.
                # This will be validated by the tenant.
                ek509 = load_der_x509_certificate(
                    base64.b64decode(ekcert),
                    backend=default_backend(),
                )
                ek_tpm = base64.b64encode(
                    tpm2_objects.ek_low_tpm2b_public_from_pubkey(
                        ek509.public_key(), )).decode()

            aik_attrs = tpm2_objects.get_tpm2b_public_object_attributes(
                base64.b64decode(aik_tpm), )
            if aik_attrs != tpm2_objects.AK_EXPECTED_ATTRS:
                web_util.echo_json_response(self, 400, "Invalid AK attributes")
                logger.warning(
                    "Agent %s submitted AIK with invalid attributes! %s (provided) != %s (expected)",
                    agent_id,
                    tpm2_objects.object_attributes_description(aik_attrs),
                    tpm2_objects.object_attributes_description(
                        tpm2_objects.AK_EXPECTED_ATTRS),
                )
                return

            # try to encrypt the AIK
            (blob, key) = initialize_tpm.encryptAIK(
                agent_id,
                base64.b64decode(ek_tpm),
                base64.b64decode(aik_tpm),
            )

            # special behavior if we've registered this uuid before
            regcount = 1
            try:
                agent = session.query(RegistrarMain).filter_by(
                    agent_id=agent_id).first()
            except NoResultFound:
                agent = None
            except SQLAlchemyError as e:
                logger.error('SQLAlchemy Error: %s', e)
                raise

            if agent is not None:

                # keep track of how many ek-ekcerts have registered on this uuid
                regcount = agent.regcount
                if agent.ek_tpm != ek_tpm or agent.ekcert != ekcert:
                    logger.warning(
                        'WARNING: Overwriting previous registration for this UUID with new ek-ekcert pair!'
                    )
                    regcount += 1

                # force overwrite
                logger.info('Overwriting previous registration for this UUID.')
                try:
                    session.query(RegistrarMain).filter_by(
                        agent_id=agent_id).delete()
                    session.commit()
                except SQLAlchemyError as e:
                    logger.error('SQLAlchemy Error: %s', e)
                    raise
            # Check for ip and port
            contact_ip = json_body.get('ip', None)
            contact_port = json_body.get('port', None)

            # Validate ip and port
            if contact_ip is not None:
                try:
                    # Use parser from the standard library instead of implementing our own
                    ipaddress.ip_address(contact_ip)
                except ValueError:
                    logger.warning(
                        f"Contact ip for agent {agent_id} is not a valid ip got: {contact_ip}."
                    )
                    contact_ip = None
            if contact_port is not None:
                try:
                    contact_port = int(contact_port)
                    if contact_port < 1 or contact_port > 65535:
                        logger.warning(
                            f"Contact port for agent {agent_id} is not a number between 1 and got: {contact_port}."
                        )
                        contact_port = None
                except ValueError:
                    logger.warning(
                        f"Contact port for agent {agent_id} is not a valid number got: {contact_port}."
                    )
                    contact_port = None

            # Check for mTLS cert
            mtls_cert = json_body.get('mtls_cert', None)
            if mtls_cert is None:
                logger.warning(
                    f"Agent {agent_id} did not sent a mTLS certificate. Most operations will not work!"
                )

            # Add values to database
            d = {}
            d['agent_id'] = agent_id
            d['ek_tpm'] = ek_tpm
            d['aik_tpm'] = aik_tpm
            d['ekcert'] = ekcert
            d['ip'] = contact_ip
            d['mtls_cert'] = mtls_cert
            d['port'] = contact_port
            d['virtual'] = int(ekcert == 'virtual')
            d['active'] = int(False)
            d['key'] = key
            d['provider_keys'] = {}
            d['regcount'] = regcount

            try:
                session.add(RegistrarMain(**d))
                session.commit()
            except SQLAlchemyError as e:
                logger.error('SQLAlchemy Error: %s', e)
                raise

            response = {
                'blob': blob,
            }
            web_util.echo_json_response(self, 200, "Success", response)

            logger.info('POST returning key blob for agent_id: %s', agent_id)
        except Exception as e:
            web_util.echo_json_response(self, 400, "Error: %s" % e)
            logger.warning("POST for %s returning 400 response. Error: %s",
                           agent_id, e)
            logger.exception(e)
示例#2
0
 def setUp(self):
     self.tpm = tpm()
示例#3
0
from keylime import crypto
from keylime import openstack
from keylime import revocation_notifier
from keylime import registrar_client
from keylime import secure_mount
from keylime.tpm.tpm_main import tpm
from keylime.tpm.tpm_abstract import TPM_Utilities

# Configure logger
logger = keylime_logging.init_logging('cloudagent')

# lock required for multithreaded operation
uvLock = threading.Lock()

# Instaniate tpm
tpm_instance = tpm(need_hw_tpm=True)


class Handler(BaseHTTPRequestHandler):
    parsed_path = ''

    def do_HEAD(self):
        """Not supported"""
        config.echo_json_response(self, 405, "HEAD not supported")

    def do_GET(self):
        """This method services the GET request typically from either the Tenant or the Cloud Verifier.

        Only tenant and cloudverifier uri's are supported. Both requests require a nonce parameter.
        The Cloud verifier requires an additional mask paramter.  If the uri or parameters are incorrect, a 400 response is returned.
        """
示例#4
0
def main():
    for ML in [config.MEASUREDBOOT_ML, config.IMA_ML]:
        if not os.access(ML, os.F_OK):
            logger.warning(
                "Measurement list path %s not accessible by agent. Any attempt to instruct it to access this path - via \"keylime_tenant\" CLI - will result in agent process dying",
                ML)

    if config.get('cloud_agent', 'agent_uuid') == 'dmidecode':
        if os.getuid() != 0:
            raise RuntimeError('agent_uuid is configured to use dmidecode, '
                               'but current process is not running as root.')
        cmd = ['which', 'dmidecode']
        ret = cmd_exec.run(cmd, raiseOnError=False)
        if ret['code'] != 0:
            raise RuntimeError('agent_uuid is configured to use dmidecode, '
                               'but it\'s is not found on the system.')

    # Instanitate TPM class

    instance_tpm = tpm()
    # get params for initialization
    registrar_ip = config.get('cloud_agent', 'registrar_ip')
    registrar_port = config.get('cloud_agent', 'registrar_port')

    # initialize the tmpfs partition to store keys if it isn't already available
    secdir = secure_mount.mount()

    # change dir to working dir
    config.ch_dir(config.WORK_DIR, logger)

    # initialize tpm
    (ekcert, ek_tpm, aik_tpm) = instance_tpm.tpm_init(
        self_activate=False,
        config_pw=config.get('cloud_agent', 'tpm_ownerpassword')
    )  # this tells initialize not to self activate the AIK
    virtual_agent = instance_tpm.is_vtpm()
    # try to get some TPM randomness into the system entropy pool
    instance_tpm.init_system_rand()

    if ekcert is None:
        if virtual_agent:
            ekcert = 'virtual'
        elif instance_tpm.is_emulator():
            ekcert = 'emulator'

    # now we need the UUID
    try:
        agent_uuid = config.get('cloud_agent', 'agent_uuid')
    except configparser.NoOptionError:
        agent_uuid = None
    if agent_uuid == 'openstack':
        agent_uuid = openstack.get_openstack_uuid()
    elif agent_uuid == 'hash_ek':
        agent_uuid = hashlib.sha256(ek_tpm).hexdigest()
    elif agent_uuid == 'generate' or agent_uuid is None:
        agent_uuid = str(uuid.uuid4())
    elif agent_uuid == 'dmidecode':
        cmd = ['dmidecode', '-s', 'system-uuid']
        ret = cmd_exec.run(cmd)
        sys_uuid = ret['retout'].decode('utf-8')
        agent_uuid = sys_uuid.strip()
    elif agent_uuid == 'hostname':
        agent_uuid = socket.getfqdn()
    if config.STUB_VTPM and config.TPM_CANNED_VALUES is not None:
        # Use canned values for stubbing
        jsonIn = config.TPM_CANNED_VALUES
        if "add_vtpm_to_group" in jsonIn:
            # The value we're looking for has been canned!
            agent_uuid = jsonIn['add_vtpm_to_group']['retout']
        else:
            # Our command hasn't been canned!
            raise Exception("Command %s not found in canned json!" %
                            ("add_vtpm_to_group"))

    logger.info("Agent UUID: %s", agent_uuid)

    # register it and get back a blob
    keyblob = registrar_client.doRegisterAgent(registrar_ip, registrar_port,
                                               agent_uuid, ek_tpm, ekcert,
                                               aik_tpm)

    if keyblob is None:
        instance_tpm.flush_keys()
        raise Exception("Registration failed")

    # get the ephemeral registrar key
    key = instance_tpm.activate_identity(keyblob)

    if key is None:
        instance_tpm.flush_keys()
        raise Exception("Activation failed")

    # tell the registrar server we know the key
    retval = False
    retval = registrar_client.doActivateAgent(registrar_ip, registrar_port,
                                              agent_uuid, key)

    if not retval:
        instance_tpm.flush_keys()
        raise Exception("Registration failed on activate")

    serveraddr = (config.get('cloud_agent', 'cloudagent_ip'),
                  config.getint('cloud_agent', 'cloudagent_port'))
    server = CloudAgentHTTPServer(serveraddr, Handler, agent_uuid)
    serverthread = threading.Thread(target=server.serve_forever)

    logger.info("Starting Cloud Agent on %s:%s use <Ctrl-C> to stop",
                serveraddr[0], serveraddr[1])
    serverthread.start()

    # want to listen for revocations?
    if config.getboolean('cloud_agent', 'listen_notfications'):
        cert_path = config.get('cloud_agent', 'revocation_cert')
        if cert_path == "default":
            cert_path = '%s/unzipped/RevocationNotifier-cert.crt' % (secdir)
        elif cert_path[0] != '/':
            # if it is a relative, convert to absolute in work_dir
            cert_path = os.path.abspath('%s/%s' % (config.WORK_DIR, cert_path))

        def perform_actions(revocation):
            actionlist = []

            # load the actions from inside the keylime module
            actionlisttxt = config.get('cloud_agent', 'revocation_actions')
            if actionlisttxt.strip() != "":
                actionlist = actionlisttxt.split(',')
                actionlist = ["revocation_actions.%s" % i for i in actionlist]

            # load actions from unzipped
            if os.path.exists("%s/unzipped/action_list" % secdir):
                with open("%s/unzipped/action_list" % secdir, 'r') as f:
                    actionlisttxt = f.read()
                if actionlisttxt.strip() != "":
                    localactions = actionlisttxt.strip().split(',')
                    for action in localactions:
                        if not action.startswith('local_action_'):
                            logger.warning(
                                "Invalid local action: %s. Must start with local_action_",
                                action)
                        else:
                            actionlist.append(action)

                    uzpath = "%s/unzipped" % secdir
                    if uzpath not in sys.path:
                        sys.path.append(uzpath)

            for action in actionlist:
                logger.info("Executing revocation action %s", action)
                try:
                    module = importlib.import_module(action)
                    execute = getattr(module, 'execute')
                    asyncio.get_event_loop().run_until_complete(
                        execute(revocation))
                except Exception as e:
                    logger.warning(
                        "Exception during execution of revocation action %s: %s",
                        action, e)

        try:
            while True:
                try:
                    revocation_notifier.await_notifications(
                        perform_actions, revocation_cert_path=cert_path)
                except Exception as e:
                    logger.exception(e)
                    logger.warning(
                        "No connection to revocation server, retrying in 10s..."
                    )
                    time.sleep(10)
        except KeyboardInterrupt:
            logger.info("TERM Signal received, shutting down...")
            instance_tpm.flush_keys()
            server.shutdown()
    else:
        try:
            while True:
                time.sleep(1)
        except KeyboardInterrupt:
            logger.info("TERM Signal received, shutting down...")
            instance_tpm.flush_keys()
            server.shutdown()
示例#5
0
def main():
    for ML in [config.MEASUREDBOOT_ML, config.IMA_ML]:
        if not os.access(ML, os.F_OK):
            logger.warning(
                'Measurement list path %s not accessible by agent. Any attempt to instruct it to access this path - via "keylime_tenant" CLI - will result in agent process dying',
                ML,
            )

    ima_log_file = None
    if os.path.exists(config.IMA_ML):
        ima_log_file = open(config.IMA_ML, "r", encoding="utf-8")  # pylint: disable=consider-using-with

    tpm_log_file_data = None
    if os.path.exists(config.MEASUREDBOOT_ML):
        with open(config.MEASUREDBOOT_ML, "rb") as tpm_log_file:
            tpm_log_file_data = base64.b64encode(tpm_log_file.read())

    if config.get("cloud_agent", "agent_uuid") == "dmidecode":
        if os.getuid() != 0:
            raise RuntimeError("agent_uuid is configured to use dmidecode, but current process is not running as root.")
        cmd = ["which", "dmidecode"]
        ret = cmd_exec.run(cmd, raiseOnError=False)
        if ret["code"] != 0:
            raise RuntimeError("agent_uuid is configured to use dmidecode, but it's is not found on the system.")

    # initialize the tmpfs partition to store keys if it isn't already available
    secdir = secure_mount.mount()

    # Now that operations requiring root privileges are done, drop privileges
    # if 'run_as' is available in the configuration.
    if os.getuid() == 0:
        run_as = config.get("cloud_agent", "run_as", fallback="")
        if run_as != "":
            user_utils.chown(secdir, run_as)
            user_utils.change_uidgid(run_as)
            logger.info("Dropped privileges to %s", run_as)
        else:
            logger.warning("Cannot drop privileges since 'run_as' is empty or missing in keylime.conf agent section.")

    # Instanitate TPM class

    instance_tpm = tpm()
    # get params for initialization
    registrar_ip = config.get("cloud_agent", "registrar_ip")
    registrar_port = config.get("cloud_agent", "registrar_port")

    # get params for the verifier to contact the agent
    contact_ip = os.getenv("KEYLIME_AGENT_CONTACT_IP", None)
    if contact_ip is None and config.has_option("cloud_agent", "agent_contact_ip"):
        contact_ip = config.get("cloud_agent", "agent_contact_ip")
    contact_port = os.getenv("KEYLIME_AGENT_CONTACT_PORT", None)
    if contact_port is None and config.has_option("cloud_agent", "agent_contact_port"):
        contact_port = config.get("cloud_agent", "agent_contact_port", fallback="invalid")

    # change dir to working dir
    fs_util.ch_dir(config.WORK_DIR)

    # set a conservative general umask
    os.umask(0o077)

    # initialize tpm
    (ekcert, ek_tpm, aik_tpm) = instance_tpm.tpm_init(
        self_activate=False, config_pw=config.get("cloud_agent", "tpm_ownerpassword")
    )  # this tells initialize not to self activate the AIK

    # Warn if kernel version is <5.10 and another algorithm than SHA1 is used,
    # because otherwise IMA will not work
    kernel_version = tuple(platform.release().split("-")[0].split("."))
    if tuple(map(int, kernel_version)) < (5, 10, 0) and instance_tpm.defaults["hash"] != algorithms.Hash.SHA1:
        logger.warning(
            "IMA attestation only works on kernel versions <5.10 with SHA1 as hash algorithm. "
            'Even if ascii_runtime_measurements shows "%s" as the '
            "algorithm, it might be just padding zeros",
            (instance_tpm.defaults["hash"]),
        )

    if ekcert is None and instance_tpm.is_emulator():
        ekcert = "emulator"

    # now we need the UUID
    try:
        agent_uuid = config.get("cloud_agent", "agent_uuid")
    except configparser.NoOptionError:
        agent_uuid = None
    if agent_uuid == "hash_ek":
        ek_pubkey = pubkey_from_tpm2b_public(base64.b64decode(ek_tpm))
        ek_pubkey_pem = ek_pubkey.public_bytes(
            encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo
        )
        agent_uuid = hashlib.sha256(ek_pubkey_pem).hexdigest()
    elif agent_uuid == "generate" or agent_uuid is None:
        agent_uuid = str(uuid.uuid4())
    elif agent_uuid == "dmidecode":
        cmd = ["dmidecode", "-s", "system-uuid"]
        ret = cmd_exec.run(cmd)
        sys_uuid = ret["retout"][0].decode("utf-8")
        agent_uuid = sys_uuid.strip()
        try:
            uuid.UUID(agent_uuid)
        except ValueError as e:
            raise RuntimeError(  # pylint: disable=raise-missing-from
                f"The UUID returned from dmidecode is invalid: {str(e)}"
            )
    elif agent_uuid == "hostname":
        agent_uuid = socket.getfqdn()
    elif agent_uuid == "environment":
        agent_uuid = os.getenv("KEYLIME_AGENT_UUID", None)
        if agent_uuid is None:
            raise RuntimeError("Env variable KEYLIME_AGENT_UUID is empty, but agent_uuid is set to 'environment'")
    elif not validators.valid_uuid(agent_uuid):
        raise RuntimeError("The UUID is not valid")

    if not validators.valid_agent_id(agent_uuid):
        raise RuntimeError("The agent ID set via agent uuid parameter use invalid characters")

    logger.info("Agent UUID: %s", agent_uuid)

    serveraddr = (config.get("cloud_agent", "cloudagent_ip"), config.getint("cloud_agent", "cloudagent_port"))

    keylime_ca = config.get("cloud_agent", "keylime_ca")
    if keylime_ca == "default":
        keylime_ca = os.path.join(config.WORK_DIR, "cv_ca", "cacert.crt")

    server = CloudAgentHTTPServer(serveraddr, Handler, agent_uuid, contact_ip, ima_log_file, tpm_log_file_data)
    if server.mtls_cert_enabled:
        context = web_util.generate_mtls_context(server.mtls_cert_path, server.rsakey_path, keylime_ca, logger=logger)
        server.socket = context.wrap_socket(server.socket, server_side=True)
    else:
        if (
            not config.getboolean("cloud_agent", "enable_insecure_payload", fallback=False)
            and config.get("cloud_agent", "payload_script") != ""
        ):
            raise RuntimeError(
                "agent mTLS is disabled, while a tenant can instruct the agent to execute code on the node. "
                'In order to allow the running of the agent, "enable_insecure_payload" has to be set to "True"'
            )

    serverthread = threading.Thread(target=server.serve_forever, daemon=True)

    # register it and get back a blob
    mtls_cert = "disabled"
    if server.mtls_cert:
        mtls_cert = server.mtls_cert.public_bytes(serialization.Encoding.PEM)

    keyblob = registrar_client.doRegisterAgent(
        registrar_ip, registrar_port, agent_uuid, ek_tpm, ekcert, aik_tpm, mtls_cert, contact_ip, contact_port
    )

    if keyblob is None:
        instance_tpm.flush_keys()
        raise Exception("Registration failed")

    # get the ephemeral registrar key
    key = instance_tpm.activate_identity(keyblob)

    if key is None:
        instance_tpm.flush_keys()
        raise Exception("Activation failed")

    # tell the registrar server we know the key
    retval = registrar_client.doActivateAgent(registrar_ip, registrar_port, agent_uuid, key)

    if not retval:
        instance_tpm.flush_keys()
        raise Exception("Registration failed on activate")

    # Start revocation listener in a new process to not interfere with tornado
    revocation_process = multiprocessing.Process(target=revocation_listener, daemon=True)
    revocation_process.start()

    logger.info(
        "Starting Cloud Agent on %s:%s with API version %s. Use <Ctrl-C> to stop",
        serveraddr[0],
        serveraddr[1],
        keylime_api_version.current_version(),
    )
    serverthread.start()

    def shutdown_handler(*_):
        logger.info("TERM Signal received, shutting down...")
        logger.debug("Stopping revocation notifier...")
        revocation_process.terminate()
        logger.debug("Shutting down HTTP server...")
        server.shutdown()
        server.server_close()
        serverthread.join()
        logger.debug("HTTP server stopped...")
        revocation_process.join()
        logger.debug("Revocation notifier stopped...")
        secure_mount.umount()
        logger.debug("Umounting directories...")
        instance_tpm.flush_keys()
        logger.debug("Flushed keys successfully")
        sys.exit(0)

    signal.signal(signal.SIGTERM, shutdown_handler)
    signal.signal(signal.SIGQUIT, shutdown_handler)
    signal.signal(signal.SIGINT, shutdown_handler)

    # Keep the main thread alive by waiting for the server thread
    serverthread.join()
示例#6
0
class Tenant():
    """Simple command processor example."""

    config = None

    cloudverifier_ip = None
    cloudverifier_port = None

    cloudagent_ip = None
    cv_cloudagent_ip = None
    cloudagent_port = None

    registrar_ip = None
    registrar_port = None

    webapp_ip = None
    webapp_port = None

    uuid_service_generate_locally = None
    agent_uuid = None

    K = None
    V = None
    U = None
    auth_tag = None

    tpm_policy = None
    vtpm_policy = {}
    metadata = {}
    allowlist = {}
    ima_sign_verification_keys = []
    revocation_key = ""
    accept_tpm_hash_algs = []
    accept_tpm_encryption_algs = []
    accept_tpm_signing_algs = []

    payload = None

    tpm_instance = tpm()

    def __init__(self):
        """ Set up required values and TLS
        """
        self.nonce = None
        self.agent_ip = None
        self.agent_port = config.get('cloud_agent', 'cloudagent_port')
        self.verifier_ip = config.get('tenant', 'cloudverifier_ip')
        self.verifier_port = config.get('tenant', 'cloudverifier_port')
        self.registrar_ip = config.get('tenant', 'registrar_ip')
        self.registrar_port = config.get('tenant', 'registrar_port')
        self.webapp_port = config.getint('webapp', 'webapp_port')
        if not config.REQUIRE_ROOT and self.webapp_port < 1024:
            self.webapp_port += 2000
        self.webapp_ip = config.get('webapp', 'webapp_ip')

        self.my_cert, self.my_priv_key = self.get_tls_context()
        self.cert = (self.my_cert, self.my_priv_key)
        if config.getboolean('general', "enable_tls"):
            self.tls_enabled = True
        else:
            self.tls_enabled = False
            self.cert = ""
            logger.warning(
                "Warning: TLS is currently disabled, keys will be sent in the clear! This should only be used for testing."
            )

    @property
    def verifier_base_url(self):
        return f'{self.verifier_ip}:{self.verifier_port}'

    def get_tls_context(self):
        """Generate certifcate naming and path

        Returns:
            string -- my_cert (client_cert), my_priv_key (client private key)
        """
        my_cert = config.get('tenant', 'my_cert')
        my_priv_key = config.get('tenant', 'private_key')
        tls_dir = config.get('tenant', 'tls_dir')

        if tls_dir == 'default':
            my_cert = 'client-cert.crt'
            my_priv_key = 'client-private.pem'
            tls_dir = 'cv_ca'

        if tls_dir[0] != '/':
            tls_dir = os.path.abspath('%s/%s' % (config.WORK_DIR, tls_dir))

        logger.info(f"Setting up client TLS in {tls_dir}")
        my_cert = "%s/%s" % (tls_dir, my_cert)
        my_priv_key = "%s/%s" % (tls_dir, my_priv_key)

        return my_cert, my_priv_key

    def init_add(self, args):
        """ Set up required values. Command line options can overwrite these config values

        Arguments:
            args {[string]} -- agent_ip|agent_port|cv_agent_ip
        """
        if "agent_ip" in args:
            self.agent_ip = args["agent_ip"]

        if 'agent_port' in args and args['agent_port'] is not None:
            self.agent_port = args['agent_port']

        if 'cv_agent_ip' in args and args['cv_agent_ip'] is not None:
            self.cv_cloudagent_ip = args['cv_agent_ip']
        else:
            self.cv_cloudagent_ip = self.agent_ip

        # Make sure all keys exist in dictionary
        if "file" not in args:
            args["file"] = None
        if "keyfile" not in args:
            args["keyfile"] = None
        if "payload" not in args:
            args["payload"] = None
        if "ca_dir" not in args:
            args["ca_dir"] = None
        if "incl_dir" not in args:
            args["incl_dir"] = None
        if "ca_dir_pw" not in args:
            args["ca_dir_pw"] = None

        # Set up accepted algorithms
        self.accept_tpm_hash_algs = config.get(
            'tenant', 'accept_tpm_hash_algs').split(',')
        self.accept_tpm_encryption_algs = config.get(
            'tenant', 'accept_tpm_encryption_algs').split(',')
        self.accept_tpm_signing_algs = config.get(
            'tenant', 'accept_tpm_signing_algs').split(',')

        # Set up PCR values
        tpm_policy = config.get('tenant', 'tpm_policy')
        if "tpm_policy" in args and args["tpm_policy"] is not None:
            tpm_policy = args["tpm_policy"]
        self.tpm_policy = TPM_Utilities.readPolicy(tpm_policy)
        logger.info(f"TPM PCR Mask from policy is {self.tpm_policy['mask']}")

        vtpm_policy = config.get('tenant', 'vtpm_policy')
        if "vtpm_policy" in args and args["vtpm_policy"] is not None:
            vtpm_policy = args["vtpm_policy"]
        self.vtpm_policy = TPM_Utilities.readPolicy(vtpm_policy)
        logger.info(f"TPM PCR Mask from policy is {self.vtpm_policy['mask']}")

        if args.get("ima_sign_verification_keys") is not None:
            # Auto-enable IMA (or-bit mask)
            self.tpm_policy['mask'] = "0x%X" % (int(self.tpm_policy['mask'], 0)
                                                | (1 << config.IMA_PCR))

            # Add all IMA file signing verification keys to a keyring
            ima_keyring = ima_file_signatures.ImaKeyring()
            for filename in args["ima_sign_verification_keys"]:
                pubkey = ima_file_signatures.get_pubkey_from_file(filename)
                if not pubkey:
                    raise UserError("File '%s' is not a file with a key" %
                                    filename)
                ima_keyring.add_pubkey(pubkey)
            self.ima_sign_verification_keys = ima_keyring.to_string()

        # Read command-line path string allowlist
        al_data = None

        if "allowlist" in args and args["allowlist"] is not None:

            self.enforce_pcrs(list(self.tpm_policy.keys()), [config.IMA_PCR],
                              "IMA")

            # Auto-enable IMA (or-bit mask)
            self.tpm_policy['mask'] = "0x%X" % (int(self.tpm_policy['mask'], 0)
                                                | (1 << config.IMA_PCR))

            if isinstance(args["allowlist"], str):
                if args["allowlist"] == "default":
                    args["allowlist"] = config.get('tenant', 'allowlist')
                al_data = ima.read_allowlist(args["allowlist"])
            elif isinstance(args["allowlist"], list):
                al_data = args["allowlist"]
            else:
                raise UserError("Invalid allowlist provided")

        # Read command-line path string IMA exclude list
        excl_data = None
        if "ima_exclude" in args and args["ima_exclude"] is not None:
            if isinstance(args["ima_exclude"], str):
                if args["ima_exclude"] == "default":
                    args["ima_exclude"] = config.get('tenant',
                                                     'ima_excludelist')
                excl_data = ima.read_excllist(args["ima_exclude"])
            elif isinstance(args["ima_exclude"], list):
                excl_data = args["ima_exclude"]
            else:
                raise UserError("Invalid exclude list provided")

        # Set up IMA
        if TPM_Utilities.check_mask(self.tpm_policy['mask'], config.IMA_PCR) or \
                TPM_Utilities.check_mask(self.vtpm_policy['mask'], config.IMA_PCR):

            # Process allowlists
            self.allowlist = ima.process_allowlists(al_data, excl_data)

        # Read command-line path string TPM2 event log template
        if "mb_refstate" in args and args["mb_refstate"] is not None:

            self.enforce_pcrs(list(self.tpm_policy.keys()),
                              config.MEASUREDBOOT_PCRS, "measured boot")

            # Auto-enable TPM2 event log (or-bit mask)
            for _pcr in config.MEASUREDBOOT_PCRS:
                self.tpm_policy['mask'] = "0x%X" % (int(
                    self.tpm_policy['mask'], 0) | (1 << _pcr))

            logger.info(
                f"TPM PCR Mask automatically modified is {self.tpm_policy['mask']} to include IMA/Event log PCRs"
            )

            if isinstance(args["mb_refstate"], str):
                if args["mb_refstate"] == "default":
                    args["mb_refstate"] = config.get('tenant', 'mb_refstate')
                al_data = 'test'
            elif isinstance(args["mb_refstate"], list):
                al_data = args["mb_refstate"]
            else:
                raise UserError(
                    "Invalid measured boot reference state (intended state) provided"
                )

        # if none
        if (args["file"] is None and args["keyfile"] is None
                and args["ca_dir"] is None):
            raise UserError(
                "You must specify one of -k, -f, or --cert to specify the key/contents to be securely delivered to the agent"
            )

        if args["keyfile"] is not None:
            if args["file"] is not None or args["ca_dir"] is not None:
                raise UserError(
                    "You must specify one of -k, -f, or --cert to specify the key/contents to be securely delivered to the agent"
                )

            # read the keys in
            if isinstance(args["keyfile"], dict) and "data" in args["keyfile"]:
                if isinstance(args["keyfile"]["data"], list) and len(
                        args["keyfile"]["data"]) == 1:
                    keyfile = args["keyfile"]["data"][0]
                    if keyfile is None:
                        raise UserError("Invalid key file contents")
                    f = io.StringIO(keyfile)
                else:
                    raise UserError("Invalid key file provided")
            else:
                f = open(args["keyfile"], 'r')
            self.K = base64.b64decode(f.readline())
            self.U = base64.b64decode(f.readline())
            self.V = base64.b64decode(f.readline())
            f.close()

            # read the payload in (opt.)
            if isinstance(args["payload"], dict) and "data" in args["payload"]:
                if isinstance(args["payload"]["data"],
                              list) and len(args["payload"]["data"]) > 0:
                    self.payload = args["payload"]["data"][0]
            else:
                if args["payload"] is not None:
                    f = open(args["payload"], 'r')
                    self.payload = f.read()
                    f.close()

        if args["file"] is not None:
            if args["keyfile"] is not None or args["ca_dir"] is not None:
                raise UserError(
                    "You must specify one of -k, -f, or --cert to specify the key/contents to be securely delivered to the agent"
                )

            if isinstance(args["file"], dict) and "data" in args["file"]:
                if isinstance(args["file"]["data"],
                              list) and len(args["file"]["data"]) > 0:
                    contents = args["file"]["data"][0]
                    if contents is None:
                        raise UserError("Invalid file payload contents")
                else:
                    raise UserError("Invalid file payload provided")
            else:
                with open(args["file"], 'r') as f:
                    contents = f.read()
            ret = user_data_encrypt.encrypt(contents)
            self.K = ret['k']
            self.U = ret['u']
            self.V = ret['v']
            self.payload = ret['ciphertext']

        if args["ca_dir"] is None and args["incl_dir"] is not None:
            raise UserError(
                "--include option is only valid when used with --cert")
        if args["ca_dir"] is not None:
            if args["file"] is not None or args["keyfile"] is not None:
                raise UserError(
                    "You must specify one of -k, -f, or --cert to specify the key/contents to be securely delivered to the agent"
                )
            if args["ca_dir"] == 'default':
                args["ca_dir"] = config.CA_WORK_DIR

            if "ca_dir_pw" in args and args["ca_dir_pw"] is not None:
                ca_util.setpassword(args["ca_dir_pw"])

            if not os.path.exists(args["ca_dir"]) or not os.path.exists(
                    "%s/cacert.crt" % args["ca_dir"]):
                logger.warning(" CA directory does not exist.  Creating...")
                ca_util.cmd_init(args["ca_dir"])
            if not os.path.exists("%s/%s-private.pem" %
                                  (args["ca_dir"], self.agent_uuid)):
                ca_util.cmd_mkcert(args["ca_dir"], self.agent_uuid)

            cert_pkg, serial, subject = ca_util.cmd_certpkg(
                args["ca_dir"], self.agent_uuid)

            # support revocation
            if not os.path.exists(
                    "%s/RevocationNotifier-private.pem" % args["ca_dir"]):
                ca_util.cmd_mkcert(args["ca_dir"], "RevocationNotifier")
            rev_package, _, _ = ca_util.cmd_certpkg(args["ca_dir"],
                                                    "RevocationNotifier")

            # extract public and private keys from package
            sf = io.BytesIO(rev_package)
            with zipfile.ZipFile(sf) as zf:
                privkey = zf.read("RevocationNotifier-private.pem")
                cert = zf.read("RevocationNotifier-cert.crt")

            # put the cert of the revoker into the cert package
            sf = io.BytesIO(cert_pkg)
            with zipfile.ZipFile(sf, 'a',
                                 compression=zipfile.ZIP_STORED) as zf:
                zf.writestr('RevocationNotifier-cert.crt', cert)

                # add additional files to zip
                if args["incl_dir"] is not None:
                    if isinstance(args["incl_dir"], dict) and "data" in args[
                            "incl_dir"] and "name" in args["incl_dir"]:
                        if isinstance(args["incl_dir"]["data"],
                                      list) and isinstance(
                                          args["incl_dir"]["name"], list):
                            if len(args["incl_dir"]["data"]) != len(
                                    args["incl_dir"]["name"]):
                                raise UserError("Invalid incl_dir provided")
                            for i in range(len(args["incl_dir"]["data"])):
                                zf.writestr(
                                    os.path.basename(
                                        args["incl_dir"]["name"][i]),
                                    args["incl_dir"]["data"][i])
                    else:
                        if os.path.exists(args["incl_dir"]):
                            files = next(os.walk(args["incl_dir"]))[2]
                            for filename in files:
                                with open(
                                        "%s/%s" % (args["incl_dir"], filename),
                                        'rb') as f:
                                    zf.writestr(os.path.basename(f.name),
                                                f.read())
                        else:
                            logger.warning(
                                f'Specified include directory {args["incl_dir"]} does not exist.  Skipping...'
                            )

            cert_pkg = sf.getvalue()

            # put the private key into the data to be send to the CV
            self.revocation_key = privkey

            # encrypt up the cert package
            ret = user_data_encrypt.encrypt(cert_pkg)
            self.K = ret['k']
            self.U = ret['u']
            self.V = ret['v']
            self.metadata = {'cert_serial': serial, 'subject': subject}
            self.payload = ret['ciphertext']

        if self.payload is not None and len(self.payload) > config.getint(
                'tenant', 'max_payload_size'):
            raise UserError("Payload size %s exceeds max size %d" % (len(
                self.payload), config.getint('tenant', 'max_payload_size')))

    def enforce_pcrs(self, policy_pcrs, protected_pcrs, pcr_use):
        policy_pcrs = list(self.tpm_policy.keys())
        policy_pcrs.remove('mask')

        for _pcr in policy_pcrs:
            if int(_pcr) in protected_pcrs:
                logger.error(
                    f"WARNING: PCR {_pcr} is specified in \"tpm_policy\", but will in fact be used by {pcr_use}. Please remove it from policy"
                )
                sys.exit(1)

    def preloop(self):
        """ encrypt the agent UUID as a check for delivering the correct key
        """
        self.auth_tag = crypto.do_hmac(self.K, self.agent_uuid)
        # be very careful printing K, U, or V as they leak in logs stored on unprotected disks
        if config.INSECURE_DEBUG:
            logger.debug(F"K: {base64.b64encode(self.K)}")
            logger.debug(F"V: {base64.b64encode(self.V)}")
            logger.debug(F"U: {base64.b64encode(self.U)}")
            logger.debug(F"Auth Tag: {self.auth_tag}")

    def check_ek(self, ekcert):
        """ Check the Entity Key

        Arguments:
            ekcert {str} -- The endorsement key, either None, "emulator", or base64 encoded der cert

        Returns:
            [type] -- [description]
        """
        if config.getboolean('tenant', 'require_ek_cert'):
            if config.STUB_TPM:
                logger.debug("not checking ekcert due to STUB_TPM mode")
            elif ekcert == 'emulator' and config.DISABLE_EK_CERT_CHECK_EMULATOR:
                logger.info("not checking ekcert of TPM emulator")
            elif ekcert is None:
                logger.warning(
                    "No EK cert provided, require_ek_cert option in config set to True"
                )
                return False
            elif not self.tpm_instance.verify_ek(base64.b64decode(ekcert)):
                logger.warning("Invalid EK certificate")
                return False

        return True

    def validate_tpm_quote(self, public_key, quote, hash_alg):
        """ Validate TPM Quote received from the Agent

        Arguments:
            public_key {[type]} -- [description]
            quote {[type]} -- [description]
            hash_alg {bool} -- [description]

        Raises:
            UserError: [description]

        Returns:
            [type] -- [description]
        """
        registrar_client.init_client_tls('tenant')
        reg_keys = registrar_client.getKeys(self.registrar_ip,
                                            self.registrar_port,
                                            self.agent_uuid)
        if reg_keys is None:
            logger.warning("AIK not found in registrar, quote not validated")
            return False

        if not self.tpm_instance.check_quote(self.agent_uuid,
                                             self.nonce,
                                             public_key,
                                             quote,
                                             reg_keys['aik_tpm'],
                                             hash_alg=hash_alg):
            if reg_keys['regcount'] > 1:
                logger.error(
                    "WARNING: This UUID had more than one ek-ekcert registered to it!  This might indicate that your system is misconfigured or a malicious host is present.  Run 'regdelete' for this agent and restart"
                )
                sys.exit()
            return False

        if reg_keys['regcount'] > 1:
            logger.warning(
                "WARNING: This UUID had more than one ek-ekcert registered to it!  This might indicate that your system is misconfigured.  Run 'regdelete' for this agent and restart"
            )

        if not config.STUB_TPM and (
                not config.getboolean('tenant', 'require_ek_cert')
                and config.get('tenant', 'ek_check_script') == ""):
            logger.warning(
                "DANGER: EK cert checking is disabled and no additional checks on EKs have been specified with ek_check_script option. Keylime is not secure!!"
            )

        # check EK cert and make sure it matches EK
        if not self.check_ek(reg_keys['ekcert']):
            return False
        # if agent is virtual, check phyisical EK cert and make sure it matches phyiscal EK
        if 'provider_keys' in reg_keys:
            if not self.check_ek(reg_keys['provider_keys']['ekcert']):
                return False

        # check all EKs with optional script:
        script = config.get('tenant', 'ek_check_script')
        if not script:
            return True

        if script[0] != '/':
            script = "%s/%s" % (config.WORK_DIR, script)

        logger.info(f"Checking EK with script {script}")
        # now we need to exec the script with the ek and ek cert in vars
        env = os.environ.copy()
        env['AGENT_UUID'] = self.agent_uuid
        env['EK'] = tpm2_objects.pubkey_from_tpm2b_public(
            reg_keys['ek_tpm'], ).public_bytes(
                crypto_serialization.Encoding.PEM,
                crypto_serialization.PublicFormat.SubjectPublicKeyInfo,
            )
        env['EK_TPM'] = reg_keys['ek_tpm']
        if reg_keys['ekcert'] is not None:
            env['EK_CERT'] = reg_keys['ekcert']
        else:
            env['EK_CERT'] = ""

        env['PROVKEYS'] = json.dumps(reg_keys.get('provider_keys', {}))
        proc = subprocess.Popen(script,
                                env=env,
                                shell=True,
                                cwd=config.WORK_DIR,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.STDOUT)
        retval = proc.wait()
        if retval != 0:
            raise UserError("External check script failed to validate EK")
        logger.debug("External check script successfully to validated EK")
        while True:
            line = proc.stdout.readline().decode()
            if line == "":
                break
            logger.debug(f"ek_check output: {line.strip()}")
        return True

    def do_cv(self):
        """ Initiaite v, agent_id and ip and initiate the cloudinit sequence
        """
        b64_v = base64.b64encode(self.V).decode('utf-8')
        logger.debug("b64_v:" + b64_v)
        data = {
            'v':
            b64_v,
            'cloudagent_ip':
            self.cv_cloudagent_ip,
            'cloudagent_port':
            self.agent_port,
            'tpm_policy':
            json.dumps(self.tpm_policy),
            'vtpm_policy':
            json.dumps(self.vtpm_policy),
            'allowlist':
            json.dumps(self.allowlist),
            'ima_sign_verification_keys':
            json.dumps(self.ima_sign_verification_keys),
            'metadata':
            json.dumps(self.metadata),
            'revocation_key':
            self.revocation_key,
            'accept_tpm_hash_algs':
            self.accept_tpm_hash_algs,
            'accept_tpm_encryption_algs':
            self.accept_tpm_encryption_algs,
            'accept_tpm_signing_algs':
            self.accept_tpm_signing_algs,
        }
        json_message = json.dumps(data)
        do_cv = RequestsClient(self.verifier_base_url, self.tls_enabled)
        response = do_cv.post((f'/agents/{self.agent_uuid}'),
                              data=json_message,
                              cert=self.cert,
                              verify=False)

        if response.status_code == 503:
            logger.error(
                f"Cannot connect to Verifier at {self.verifier_ip} with Port {self.verifier_port}. Connection refused."
            )
            sys.exit()
        elif response.status_code == 504:
            logger.error(
                f"Verifier at {self.verifier_ip} with Port {self.verifier_port} timed out."
            )
            sys.exit()

        if response.status_code == 409:
            # this is a conflict, need to update or delete it
            logger.error(
                f"Agent {self.agent_uuid} already existed at CV.  Please use delete or update."
            )
            sys.exit()
        elif response.status_code != 200:
            keylime_logging.log_http_response(logger, logging.ERROR,
                                              response.json())
            logger.error(
                f"POST command response: {response.status_code} Unexpected response from Cloud Verifier: {response.text}"
            )
            sys.exit()

    def do_cvstatus(self, listing=False):
        """ Perform opertional state look up for agent

        Keyword Arguments:
            listing {bool} -- If True, list all agent statues (default: {False})
        """
        agent_uuid = ""
        if not listing:
            agent_uuid = self.agent_uuid

        do_cvstatus = RequestsClient(self.verifier_base_url, self.tls_enabled)
        response = do_cvstatus.get((f'/agents/{agent_uuid}'),
                                   cert=self.cert,
                                   verify=False)

        if response.status_code == 503:
            logger.error(
                f"Cannot connect to Verifier at {self.verifier_ip} with Port {self.verifier_port}. Connection refused."
            )
            sys.exit()
        elif response == 504:
            logger.error(
                f"Verifier at {self.verifier_ip} with Port {self.verifier_port} timed out."
            )
            sys.exit()

        if response.status_code == 404:
            logger.error(
                f"Agent {agent_uuid} does not exist on the verifier. Please try to add or update agent"
            )
            sys.exit()

        if response.status_code != 200:
            logger.error(
                f"Status command response: {response.status_code}. Unexpected response from Cloud Verifier."
            )
            sys.exit()
        else:
            response_json = response.json()
            if not listing:
                operational_state = response_json["results"][
                    "operational_state"]
                logger.info(
                    f'Agent Status: "{states.state_to_str(operational_state)}"'
                )
            else:
                agent_array = response_json["results"]["uuids"]
                logger.info(f'Agents: "{agent_array}"')

    def do_cvdelete(self):
        """Delete agent from Verifier
        """
        do_cvdelete = RequestsClient(self.verifier_base_url, self.tls_enabled)
        response = do_cvdelete.delete((f'/agents/{self.agent_uuid}'),
                                      cert=self.cert,
                                      verify=False)

        if response.status_code == 503:
            logger.error(
                f"Cannot connect to Verifier at {self.verifier_ip} with Port {self.verifier_port}. Connection refused."
            )
            sys.exit()
        elif response.status_code == 504:
            logger.error(
                f"Verifier at {self.verifier_ip} with Port {self.verifier_port} timed out."
            )
            sys.exit()

        if response.status_code == 202:
            deleted = False
            for _ in range(12):
                get_cvdelete = RequestsClient(self.verifier_base_url,
                                              self.tls_enabled)
                response = get_cvdelete.get((f'/agents/{self.agent_uuid}'),
                                            cert=self.cert,
                                            verify=False)

                if response.status_code in (200, 404):
                    deleted = True
                    break
                time.sleep(.4)
            if deleted:
                logger.info(
                    f"CV completed deletion of agent {self.agent_uuid}")
            else:
                logger.error(
                    f"Timed out waiting for delete of agent {self.agent_uuid} to complete at CV"
                )
                sys.exit()
        elif response.status_code == 200:
            logger.info(f"Agent {self.agent_uuid} deleted from the CV")
        else:
            response_body = response.json()
            keylime_logging.log_http_response(logger, logging.ERROR,
                                              response_body)

    def do_reglist(self):
        """List agents from Registrar
        """
        registrar_client.init_client_tls('tenant')
        response = registrar_client.doRegistrarList(self.registrar_ip,
                                                    self.registrar_port)
        print(response)

    def do_regdelete(self):
        """ Delete agent from Registrar
        """
        registrar_client.init_client_tls('tenant')
        registrar_client.doRegistrarDelete(self.registrar_ip,
                                           self.registrar_port,
                                           self.agent_uuid)

    def do_cvreactivate(self):
        """ Reactive Agent
        """
        do_cvreactivate = RequestsClient(self.verifier_base_url,
                                         self.tls_enabled)
        response = do_cvreactivate.put(
            (f'/agents/{self.agent_uuid}/reactivate'),
            data=b'',
            cert=self.cert,
            verify=False)

        if response.status_code == 503:
            logger.error(
                f"Cannot connect to Verifier at {self.verifier_ip} with Port {self.verifier_port}. Connection refused."
            )
            sys.exit()
        elif response.status_code == 504:
            logger.error(
                f"Verifier at {self.verifier_ip} with Port {self.verifier_port} timed out."
            )
            sys.exit()

        response_body = response.json()

        if response.status_code != 200:
            keylime_logging.log_http_response(logger, logging.ERROR,
                                              response_body)
            logger.error(
                f"Update command response: {response.status_code} Unexpected response from Cloud Verifier."
            )
        else:
            logger.info(f"Agent {self.agent_uuid} re-activated")

    def do_cvstop(self):
        """ Stop declared active agent
        """
        params = f'/agents/{self.agent_uuid}/stop'
        do_cvstop = RequestsClient(self.verifier_base_url, self.tls_enabled)
        response = do_cvstop.put(params,
                                 cert=self.cert,
                                 data=b'',
                                 verify=False)

        if response.status_code == 503:
            logger.error(
                f"Cannot connect to Verifier at {self.verifier_ip} with Port {self.verifier_port}. Connection refused."
            )
            sys.exit()
        elif response.status_code == 504:
            logger.error(
                f"Verifier at {self.verifier_ip} with Port {self.verifier_port} timed out."
            )
            sys.exit()

        response_body = response.json()
        if response.status_code != 200:
            keylime_logging.log_http_response(logger, logging.ERROR,
                                              response_body)
        else:
            logger.info(f"Agent {self.agent_uuid} stopped")

    def do_quote(self):
        """ Perform TPM quote by GET towards Agent

        Raises:
            UserError: Connection handler
        """
        self.nonce = TPM_Utilities.random_password(20)

        numtries = 0
        response = None
        # Note: We need a specific retry handler (perhaps in common), no point having localised unless we have too.
        while True:
            try:
                params = '/quotes/identity?nonce=%s' % (self.nonce)
                cloudagent_base_url = f'{self.agent_ip}:{self.agent_port}'
                do_quote = RequestsClient(cloudagent_base_url,
                                          tls_enabled=False)
                response = do_quote.get(params, cert=self.cert)
                response_body = response.json()

            except Exception as e:
                if response.status_code in (503, 504):
                    numtries += 1
                    maxr = config.getint('tenant', 'max_retries')
                    if numtries >= maxr:
                        logger.error(
                            f"tenant cannot establish connection to agent on {self.agent_ip} with port {self.agent_port}"
                        )
                        sys.exit()
                    retry = config.getfloat('tenant', 'retry_interval')
                    logger.info(
                        f"tenant connection to agent at {self.agent_ip} refused {numtries}/{maxr} times, trying again in {retry} seconds..."
                    )
                    time.sleep(retry)
                    continue

                raise e
            break

        try:
            if response is not None and response.status_code != 200:
                raise UserError(
                    "Status command response: %d Unexpected response from Cloud Agent."
                    % response.status)

            if "results" not in response_body:
                raise UserError(
                    "Error: unexpected http response body from Cloud Agent: %s"
                    % str(response.status))

            quote = response_body["results"]["quote"]
            logger.debug(f"agent_quote received quote: {quote}")

            public_key = response_body["results"]["pubkey"]
            logger.debug(f"agent_quote received public key: {public_key}")

            # Ensure hash_alg is in accept_tpm_hash_algs list
            hash_alg = response_body["results"]["hash_alg"]
            logger.debug(f"agent_quote received hash algorithm: {hash_alg}")
            if not algorithms.is_accepted(
                    hash_alg,
                    config.get('tenant', 'accept_tpm_hash_algs').split(',')):
                raise UserError(
                    "TPM Quote is using an unaccepted hash algorithm: %s" %
                    hash_alg)

            # Ensure enc_alg is in accept_tpm_encryption_algs list
            enc_alg = response_body["results"]["enc_alg"]
            logger.debug(
                f"agent_quote received encryption algorithm: {enc_alg}")
            if not algorithms.is_accepted(
                    enc_alg,
                    config.get('tenant',
                               'accept_tpm_encryption_algs').split(',')):
                raise UserError(
                    "TPM Quote is using an unaccepted encryption algorithm: %s"
                    % enc_alg)

            # Ensure sign_alg is in accept_tpm_encryption_algs list
            sign_alg = response_body["results"]["sign_alg"]
            logger.debug(f"agent_quote received signing algorithm: {sign_alg}")
            if not algorithms.is_accepted(
                    sign_alg,
                    config.get('tenant',
                               'accept_tpm_signing_algs').split(',')):
                raise UserError(
                    "TPM Quote is using an unaccepted signing algorithm: %s" %
                    sign_alg)

            if not self.validate_tpm_quote(public_key, quote, hash_alg):
                raise UserError(
                    "TPM Quote from cloud agent is invalid for nonce: %s" %
                    self.nonce)

            logger.info(f"Quote from {self.agent_ip} validated")

            # encrypt U with the public key
            encrypted_U = crypto.rsa_encrypt(
                crypto.rsa_import_pubkey(public_key), self.U)

            b64_encrypted_u = base64.b64encode(encrypted_U)
            logger.debug("b64_encrypted_u: " + b64_encrypted_u.decode('utf-8'))
            data = {
                'encrypted_key': b64_encrypted_u,
                'auth_tag': self.auth_tag
            }

            if self.payload is not None:
                data['payload'] = self.payload

            u_json_message = json.dumps(data)

            # post encrypted U back to CloudAgent
            params = '/keys/ukey'
            cloudagent_base_url = (f'{self.agent_ip}:{self.agent_port}')

            post_ukey = RequestsClient(cloudagent_base_url, tls_enabled=False)
            response = post_ukey.post(params, data=u_json_message)

            if response.status_code == 503:
                logger.error(
                    f"Cannot connect to Agent at {self.agent_ip} with Port {self.agent_port}. Connection refused."
                )
                sys.exit()
            elif response.status_code == 504:
                logger.error(
                    f"Verifier at {self.verifier_ip} with Port {self.verifier_port} timed out."
                )
                sys.exit()

            if response.status_code != 200:
                keylime_logging.log_http_response(logger, logging.ERROR,
                                                  response_body)
                raise UserError(
                    "Posting of Encrypted U to the Cloud Agent failed with response code %d"
                    % response.status)
        except Exception as e:
            self.do_cvstop()
            raise e

    def do_verify(self):
        """ Perform verify using a random generated challenge
        """
        challenge = TPM_Utilities.random_password(20)
        numtries = 0
        while True:
            try:
                cloudagent_base_url = (f'{self.agent_ip}:{self.agent_port}')
                do_verify = RequestsClient(cloudagent_base_url,
                                           tls_enabled=False)
                response = do_verify.get(
                    (f'/keys/verify?challenge={challenge}'),
                    cert=self.cert,
                    verify=False)
            except Exception as e:
                if response.status_code in (503, 504):
                    numtries += 1
                    maxr = config.getint('tenant', 'max_retries')
                    if numtries >= maxr:
                        logger.error(
                            f"Cannot establish connection to agent on {self.agent_ip} with port {self.agent_port}"
                        )
                        sys.exit()
                    retry = config.getfloat('tenant', 'retry_interval')
                    logger.info(
                        f"Verifier connection to agent at {self.agent_ip} refused {numtries}/{maxr} times, trying again in {retry} seconds..."
                    )
                    time.sleep(retry)
                    continue

                raise e
            response_body = response.json()
            if response.status_code == 200:
                if "results" not in response_body or 'hmac' not in response_body[
                        'results']:
                    logger.critical(
                        f"Error: unexpected http response body from Cloud Agent: {response.status_code}"
                    )
                    break
                mac = response_body['results']['hmac']

                ex_mac = crypto.do_hmac(self.K, challenge)

                if mac == ex_mac:
                    logger.info("Key derivation successful")
                else:
                    logger.error("Key derivation failed")
            else:
                keylime_logging.log_http_response(logger, logging.ERROR,
                                                  response_body)
                retry = config.getfloat('tenant', 'retry_interval')
                logger.warning(
                    f"Key derivation not yet complete...trying again in {retry} seconds...Ctrl-C to stop"
                )
                time.sleep(retry)
                continue
            break
示例#7
0
def get_tpm_instance():
    global GLOBAL_TPM_INSTANCE
    if GLOBAL_TPM_INSTANCE is None:
        GLOBAL_TPM_INSTANCE = tpm()
    return GLOBAL_TPM_INSTANCE
示例#8
0
    def test_010_reg_agent_post(self):
        """Test registrar's POST /agents/{UUID} Interface"""
        global keyblob, vtpm, tpm_instance, ek_tpm, aik_tpm
        contact_ip = "127.0.0.1"
        contact_port = 9002
        tpm_instance = tpm_main.tpm()

        # Change CWD for TPM-related operations
        cwd = os.getcwd()
        fs_util.ch_dir(config.WORK_DIR)
        _ = secure_mount.mount()

        # Create a mTLS cert for testing
        global mtls_cert
        rsa_key = crypto.rsa_generate(2048)
        valid_util = datetime.datetime.utcnow() + datetime.timedelta(
            days=(360 * 5))
        mtls_cert = crypto.generate_selfsigned_cert(
            "TEST_CERT", rsa_key,
            valid_util).public_bytes(serialization.Encoding.PEM)

        # Initialize the TPM with AIK
        (ekcert, ek_tpm, aik_tpm) = tpm_instance.tpm_init(
            self_activate=False,
            config_pw=config.get('cloud_agent', 'tpm_ownerpassword'))
        vtpm = tpm_instance.is_vtpm()

        # Handle virtualized and emulated TPMs
        if ekcert is None:
            if vtpm:
                ekcert = 'virtual'
            elif tpm_instance.is_emulator():
                ekcert = 'emulator'

        # Get back to our original CWD
        fs_util.ch_dir(cwd)

        data = {
            'ekcert': ekcert,
            'aik_tpm': aik_tpm,
            'ip': contact_ip,
            'port': contact_port,
            'mtls_cert': mtls_cert
        }
        if ekcert is None or ekcert == 'emulator':
            data['ek_tpm'] = ek_tpm

        test_010_reg_agent_post = RequestsClient(
            tenant_templ.registrar_base_url, tls_enabled=False)
        response = test_010_reg_agent_post.post(
            f'/v{self.api_version}/agents/{tenant_templ.agent_uuid}',
            data=json.dumps(data),
            cert="",
            verify=False)

        self.assertEqual(response.status_code, 200,
                         "Non-successful Registrar agent Add return code!")
        json_response = response.json()

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

        keyblob = json_response["results"]["blob"]
        self.assertIsNotNone(keyblob, "Malformed response body!")
示例#9
0
def main():
    for ML in [config.MEASUREDBOOT_ML, config.IMA_ML]:
        if not os.access(ML, os.F_OK):
            logger.warning(
                "Measurement list path %s not accessible by agent. Any attempt to instruct it to access this path - via \"keylime_tenant\" CLI - will result in agent process dying",
                ML)

    if config.get('cloud_agent', 'agent_uuid') == 'dmidecode':
        if os.getuid() != 0:
            raise RuntimeError('agent_uuid is configured to use dmidecode, '
                               'but current process is not running as root.')
        cmd = ['which', 'dmidecode']
        ret = cmd_exec.run(cmd, raiseOnError=False)
        if ret['code'] != 0:
            raise RuntimeError('agent_uuid is configured to use dmidecode, '
                               'but it\'s is not found on the system.')

    # Instanitate TPM class

    instance_tpm = tpm()
    # get params for initialization
    registrar_ip = config.get('cloud_agent', 'registrar_ip')
    registrar_port = config.get('cloud_agent', 'registrar_port')

    # get params for the verifier to contact the agent
    contact_ip = os.getenv("KEYLIME_AGENT_CONTACT_IP", None)
    if contact_ip is None and config.has_option('cloud_agent',
                                                'agent_contact_ip'):
        contact_ip = config.get('cloud_agent', 'agent_contact_ip')
    contact_port = os.getenv("KEYLIME_AGENT_CONTACT_PORT", None)
    if contact_port is None and config.has_option('cloud_agent',
                                                  'agent_contact_port'):
        contact_port = config.get('cloud_agent',
                                  'agent_contact_port',
                                  fallback="invalid")

    # initialize the tmpfs partition to store keys if it isn't already available
    secure_mount.mount()

    # change dir to working dir
    fs_util.ch_dir(config.WORK_DIR)

    # set a conservative general umask
    os.umask(0o077)

    # initialize tpm
    (ekcert, ek_tpm, aik_tpm) = instance_tpm.tpm_init(
        self_activate=False,
        config_pw=config.get('cloud_agent', 'tpm_ownerpassword')
    )  # this tells initialize not to self activate the AIK
    virtual_agent = instance_tpm.is_vtpm()

    # Warn if kernel version is <5.10 and another algorithm than SHA1 is used,
    # because otherwise IMA will not work
    kernel_version = tuple(platform.release().split("-")[0].split("."))
    if tuple(map(int, kernel_version)) < (
            5, 10,
            0) and instance_tpm.defaults["hash"] != algorithms.Hash.SHA1:
        logger.warning(
            "IMA attestation only works on kernel versions <5.10 with SHA1 as hash algorithm. "
            "Even if ascii_runtime_measurements shows \"%s\" as the "
            "algorithm, it might be just padding zeros",
            (instance_tpm.defaults["hash"]))

    if ekcert is None:
        if virtual_agent:
            ekcert = 'virtual'
        elif instance_tpm.is_emulator():
            ekcert = 'emulator'

    # now we need the UUID
    try:
        agent_uuid = config.get('cloud_agent', 'agent_uuid')
    except configparser.NoOptionError:
        agent_uuid = None
    if agent_uuid == 'hash_ek':
        ek_pubkey = pubkey_from_tpm2b_public(base64.b64decode(ek_tpm))
        ek_pubkey_pem = ek_pubkey.public_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PublicFormat.SubjectPublicKeyInfo)
        agent_uuid = hashlib.sha256(ek_pubkey_pem).hexdigest()
    elif agent_uuid == 'generate' or agent_uuid is None:
        agent_uuid = str(uuid.uuid4())
    elif agent_uuid == 'dmidecode':
        cmd = ['dmidecode', '-s', 'system-uuid']
        ret = cmd_exec.run(cmd)
        sys_uuid = ret['retout'][0].decode('utf-8')
        agent_uuid = sys_uuid.strip()
        try:
            uuid.UUID(agent_uuid)
        except ValueError as e:
            raise RuntimeError(
                "The UUID returned from dmidecode is invalid: %s" % e)  # pylint: disable=raise-missing-from
    elif agent_uuid == 'hostname':
        agent_uuid = socket.getfqdn()
    elif agent_uuid == 'environment':
        agent_uuid = os.getenv("KEYLIME_AGENT_UUID", None)
        if agent_uuid is None:
            raise RuntimeError(
                "Env variable KEYLIME_AGENT_UUID is empty, but agent_uuid is set to 'environment'"
            )
    elif not validators.valid_uuid(agent_uuid):
        raise RuntimeError("The UUID is not valid")

    if not validators.valid_agent_id(agent_uuid):
        raise RuntimeError(
            "The agent ID set via agent uuid parameter use invalid characters")

    if config.STUB_VTPM and config.TPM_CANNED_VALUES is not None:
        # Use canned values for stubbing
        jsonIn = config.TPM_CANNED_VALUES
        if "add_vtpm_to_group" in jsonIn:
            # The value we're looking for has been canned!
            agent_uuid = jsonIn['add_vtpm_to_group']['retout']
        else:
            # Our command hasn't been canned!
            raise Exception("Command %s not found in canned json!" %
                            ("add_vtpm_to_group"))

    logger.info("Agent UUID: %s", agent_uuid)

    serveraddr = (config.get('cloud_agent', 'cloudagent_ip'),
                  config.getint('cloud_agent', 'cloudagent_port'))

    keylime_ca = config.get('cloud_agent', 'keylime_ca')
    if keylime_ca == "default":
        keylime_ca = os.path.join(config.WORK_DIR, 'cv_ca', 'cacert.crt')

    server = CloudAgentHTTPServer(serveraddr, Handler, agent_uuid)
    context = web_util.generate_mtls_context(server.mtls_cert_path,
                                             server.rsakey_path,
                                             keylime_ca,
                                             logger=logger)
    server.socket = context.wrap_socket(server.socket, server_side=True)
    serverthread = threading.Thread(target=server.serve_forever, daemon=True)

    # register it and get back a blob
    mtls_cert = server.mtls_cert.public_bytes(serialization.Encoding.PEM)
    keyblob = registrar_client.doRegisterAgent(registrar_ip, registrar_port,
                                               agent_uuid, ek_tpm, ekcert,
                                               aik_tpm, mtls_cert, contact_ip,
                                               contact_port)

    if keyblob is None:
        instance_tpm.flush_keys()
        raise Exception("Registration failed")

    # get the ephemeral registrar key
    key = instance_tpm.activate_identity(keyblob)

    if key is None:
        instance_tpm.flush_keys()
        raise Exception("Activation failed")

    # tell the registrar server we know the key
    retval = registrar_client.doActivateAgent(registrar_ip, registrar_port,
                                              agent_uuid, key)

    if not retval:
        instance_tpm.flush_keys()
        raise Exception("Registration failed on activate")

    # Start revocation listener in a new process to not interfere with tornado
    revocation_process = multiprocessing.Process(target=revocation_listener,
                                                 daemon=True)
    revocation_process.start()

    logger.info(
        "Starting Cloud Agent on %s:%s with API version %s. Use <Ctrl-C> to stop",
        serveraddr[0], serveraddr[1], keylime_api_version.current_version())
    serverthread.start()

    def shutdown_handler(*_):
        logger.info("TERM Signal received, shutting down...")
        logger.debug("Stopping revocation notifier...")
        revocation_process.terminate()
        logger.debug("Shutting down HTTP server...")
        server.shutdown()
        server.server_close()
        serverthread.join()
        logger.debug("HTTP server stopped...")
        revocation_process.join()
        logger.debug("Revocation notifier stopped...")
        secure_mount.umount()
        logger.debug("Umounting directories...")
        instance_tpm.flush_keys()
        logger.debug("Flushed keys successfully")
        sys.exit(0)

    signal.signal(signal.SIGTERM, shutdown_handler)
    signal.signal(signal.SIGQUIT, shutdown_handler)
    signal.signal(signal.SIGINT, shutdown_handler)

    # Keep the main thread alive by waiting for the server thread
    serverthread.join()