Exemple #1
0
    async def test_041_agent_keys_verify_get(self):
        """Test agent's GET /keys/verify Interface
        We use async here to allow function await while key processes"""
        self.assertIsNotNone(
            self.K, "Required value not set.  Previous step may have failed?")
        challenge = tpm_abstract.TPM_Utilities.random_password(20)
        encoded = base64.b64encode(self.K).decode("utf-8")

        response = tornado_requests.request(
            "GET",
            f"http://{self.cloudagent_ip}:{self.cloudagent_port}/keys/verify?challenge={challenge}"
        )
        response = await response
        self.assertEqual(response.status, 200,
                         "Non-successful Agent verify return code!")
        json_response = json.loads(response.read().decode())

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

        # Be sure response is valid
        mac = json_response["results"]["hmac"]
        ex_mac = crypto.do_hmac(encoded, challenge)
        # ex_mac = crypto.do_hmac(self.K, challenge)
        self.assertEqual(mac, ex_mac,
                         "Agent failed to validate challenge code!")
Exemple #2
0
    def test_011_reg_agent_activate_put(self):
        """Test registrar's PUT /agents/{UUID}/activate Interface"""

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

        key = tpm_instance.activate_identity(keyblob)
        data = {
            "auth_tag": crypto.do_hmac(key, tenant_templ.agent_uuid),
        }
        test_011_reg_agent_activate_put = RequestsClient(
            tenant_templ.registrar_base_url, tls_enabled=False)
        response = test_011_reg_agent_activate_put.put(
            f"/v{self.api_version}/agents/{tenant_templ.agent_uuid}/activate",
            data=json.dumps(data),
            cert="",
            verify=False,
        )

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

        # Ensure response is well-formed
        self.assertIn("results", json_response, "Malformed response body!")
Exemple #3
0
    def setUpClass(cls):
        """Prepare the keys and payload to give to the CV"""
        contents = "random garbage to test as payload"
        # contents = contents.encode('utf-8')
        ret = user_data_encrypt.encrypt(contents)
        cls.K = ret["k"]
        cls.U = ret["u"]
        cls.V = ret["v"]
        cls.payload = ret["ciphertext"]

        # Set up to register an agent
        cls.auth_tag = crypto.do_hmac(cls.K, tenant_templ.agent_uuid)

        # Prepare policies for agent
        cls.tpm_policy = config.get("tenant", "tpm_policy")
        cls.tpm_policy = tpm_abstract.TPM_Utilities.readPolicy(cls.tpm_policy)

        # Allow targeting a specific API version (default latest)
        cls.api_version = "2.0"

        # Set up allowlist bundles. Use invalid exclusion list regex for bad bundle.
        cls.ima_policy_bundle = ima.read_allowlist()
        cls.ima_policy_bundle["excllist"] = []

        cls.bad_ima_policy_bundle = ima.read_allowlist()
        cls.bad_ima_policy_bundle["excllist"] = ["*"]
Exemple #4
0
    def test_011_reg_agent_activate_put(self):
        """Test registrar's PUT /v2/agents/{UUID}/activate Interface"""
        global keyblob, aik

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

        key = tpm.activate_identity(keyblob)

        data = {
            'auth_tag': crypto.do_hmac(key, tenant_templ.agent_uuid),
        }
        v_json_message = json.dumps(data)

        params = f"/v{self.api_version}/agents/{tenant_templ.agent_uuid}/activate"
        response = httpclient_requests.request(
            "PUT",
            "%s" % tenant_templ.registrar_ip,
            tenant_templ.registrar_boot_port,
            params=params,
            data=v_json_message,
            context=None)
        print('response:', response)
        self.assertEqual(
            response.status, 200,
            "Non-successful Registrar agent Activate return code!")
        json_response = json.loads(response.read().decode())

        # Ensure response is well-formed
        self.assertIn("results", json_response, "Malformed response body!")
Exemple #5
0
    def decrypt_check(self, decrypted_U, decrypted_V):
        """Decrypt the Cloud init script with the passed U and V values.

        This method will access the received auth tag, and may fail if decoy U and V values were received.
        Do not call directly unless you acquire uvLock. Returns None if decryption unsuccessful, else returns the
        decrypted agent UUID.
        """

        if self.auth_tag is None:
            return None

        if len(decrypted_U) != len(decrypted_V):
            logger.warning("Invalid U len %d or V len %d. skipping...", len(decrypted_U), len(decrypted_V))
            return None

        candidate_key = crypto.strbitxor(decrypted_U, decrypted_V)

        # be very careful printing K, U, or V as they leak in logs stored on unprotected disks
        if config.INSECURE_DEBUG:
            logger.debug("U: %s", base64.b64encode(decrypted_U))
            logger.debug("V: %s", base64.b64encode(decrypted_V))
            logger.debug("K: %s", base64.b64encode(candidate_key))

        logger.debug("auth_tag: %s", self.auth_tag)
        ex_mac = crypto.do_hmac(candidate_key, self.agent_uuid)

        if ex_mac == self.auth_tag:
            logger.info("Successfully derived K for UUID %s", self.agent_uuid)
            self.final_U = decrypted_U
            self.K = candidate_key
            return True

        logger.error("Failed to derive K for UUID %s", self.agent_uuid)

        return False
Exemple #6
0
 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 common.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}")
Exemple #7
0
    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
Exemple #8
0
 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("K: %s", base64.b64encode(self.K))
         logger.debug("V: %s", base64.b64encode(self.V))
         logger.debug("U: %s", base64.b64encode(self.U))
         logger.debug("Auth Tag: %s", self.auth_tag)
Exemple #9
0
    def do_verify(self):
        challenge = TPM_Utilities.random_password(20)
        numtries = 0
        while True:
            try:
                params = f'/keys/verify?challenge={challenge}'
                response = httpclient_requests.request("GET",
                                                       "%s" %
                                                       (self.cloudagent_ip),
                                                       self.cloudagent_port,
                                                       params=params)
            except Exception as e:
                if response == 503 or 504:
                    numtries += 1
                    maxr = config.getint('tenant', 'max_retries')
                    if numtries >= maxr:
                        logger.error(
                            f"Cannot establish connection to agent on {self.cloudagent_ip} with port {self.cloudagent_port}"
                        )
                        exit()
                    retry = config.getfloat('tenant', 'retry_interval')
                    logger.info(
                        f"Verifier connection to agent at {self.cloudagent_ip} refused {numtries}/{maxr} times, trying again in {retry} seconds..."
                    )
                    time.sleep(retry)
                    continue
                else:
                    raise (e)
            response_body = json.loads(response.read().decode())
            if response.status == 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}"
                    )
                    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
Exemple #10
0
def doActivateAgent(registrar_ip,registrar_port,agent_id,key):
    data = {
    'auth_tag': crypto.do_hmac(key,agent_id),
    }
    v_json_message = json.dumps(data)
    params = '/agents/%s/activate'% (agent_id)
    response = httpclient_requests.request("PUT", "%s"%(registrar_ip), registrar_port, params=params, data=v_json_message,  context=None)
    response_body = json.loads(response.read().decode())
    if response.status == 200:
        logger.info("Registration activated for agent %s."%agent_id)
        return True
    else:
        logger.error("Error: unexpected http response code from Registrar Server: " + str(response.status))
        keylime_logging.log_http_response(logger,logging.ERROR,response_body)
        return False
Exemple #11
0
def doActivateAgent(registrar_ip, registrar_port, agent_id, key):
    data = {
        'auth_tag': crypto.do_hmac(key, agent_id),
    }
    client = RequestsClient(f'{registrar_ip}:{registrar_port}', tls_enabled)
    response = client.put(f'/agents/{agent_id}/activate', cert=tls_cert_info, data=json.dumps(data), verify=False)
    response_body = response.json()

    if response.status_code == 200:
        logger.info("Registration activated for agent %s." % agent_id)
        return True

    logger.error(
        "Error: unexpected http response code from Registrar Server: " + str(response.status_code))
    keylime_logging.log_http_response(logger, logging.ERROR, response_body)
    return False
Exemple #12
0
 def setUpClass(cls):
     """Prepare the keys and payload to give to the CV"""
     contents = "random garbage to test as payload"
     #contents = contents.encode('utf-8')
     ret = user_data_encrypt.encrypt(contents)
     cls.K = ret['k']
     cls.U = ret['u']
     cls.V = ret['v']
     cls.payload = ret['ciphertext']
     """Set up to register an agent"""
     cls.auth_tag = crypto.do_hmac(cls.K, tenant_templ.agent_uuid)
     """Prepare policies for agent"""
     cls.tpm_policy = config.get('tenant', 'tpm_policy')
     cls.vtpm_policy = config.get('tenant', 'vtpm_policy')
     cls.tpm_policy = tpm_abstract.TPM_Utilities.readPolicy(cls.tpm_policy)
     cls.vtpm_policy = tpm_abstract.TPM_Utilities.readPolicy(
         cls.vtpm_policy)
     """Allow targeting a specific API version (default latest)"""
     cls.api_version = common.API_VERSION
Exemple #13
0
def doActivateAgent(registrar_ip, registrar_port, agent_id, key):
    data = {
        "auth_tag": crypto.do_hmac(key, agent_id),
    }
    client = RequestsClient(f"{registrar_ip}:{registrar_port}",
                            tls_enabled,
                            ignore_hostname=True)
    response = client.put(f"/v{api_version}/agents/{agent_id}/activate",
                          cert=tls_cert_info,
                          data=json.dumps(data),
                          verify=ca_cert)
    response_body = response.json()

    if response.status_code == 200:
        logger.info("Registration activated for agent %s.", agent_id)
        return True

    logger.error(
        "Error: unexpected http response code from Registrar Server: %s",
        str(response.status_code))
    keylime_logging.log_http_response(logger, logging.ERROR, response_body)
    return False
    def do_PUT(self):
        """This method handles the PUT requests to add agents to the Registrar Server.

        Currently, only agents resources are available for PUTing, i.e. /agents. All other PUT uri's
        will return errors.
        """
        session = SessionManager().make_session(engine)
        rest_params = config.get_restful_params(self.path)
        if rest_params is None:
            config.echo_json_response(
                self, 405, "Not Implemented: Use /agents/ interface")
            return

        if "agents" not in rest_params:
            config.echo_json_response(self, 400, "uri not supported")
            logger.warning(
                'PUT agent returning 400 response. uri not supported: ' +
                self.path)
            return

        agent_id = rest_params["agents"]

        if agent_id is None:
            config.echo_json_response(self, 400, "agent id not found in uri")
            logger.warning(
                'PUT agent returning 400 response. agent id not found in uri '
                + self.path)
            return

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

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

            if "activate" in rest_params:
                auth_tag = json_body['auth_tag']
                try:
                    agent = session.query(RegistrarMain).filter_by(
                        agent_id=agent_id).first()
                except NoResultFound as e:
                    raise Exception(
                        "attempting to activate agent before requesting "
                        "registrar for %s" % agent_id) from e
                except SQLAlchemyError as e:
                    logger.error(f'SQLAlchemy Error: {e}')
                    raise

                if agent.virtual:
                    raise Exception(
                        "attempting to activate virtual AIK using physical interface for %s"
                        % agent_id)

                if config.STUB_TPM:
                    try:
                        session.query(RegistrarMain).filter(
                            RegistrarMain.agent_id == agent_id).update(
                                {'active': True})
                        session.commit()
                    except SQLAlchemyError as e:
                        logger.error(f'SQLAlchemy Error: {e}')
                        raise
                else:
                    # TODO(kaifeng) Special handling should be removed
                    if engine.dialect.name == "mysql":
                        agent.key = agent.key.encode('utf-8')

                    ex_mac = crypto.do_hmac(agent.key, agent_id)
                    if ex_mac == auth_tag:
                        try:
                            session.query(RegistrarMain).filter(
                                RegistrarMain.agent_id == agent_id).update(
                                    {'active': True})
                            session.commit()
                        except SQLAlchemyError as e:
                            logger.error(f'SQLAlchemy Error: {e}')
                            raise
                    else:
                        raise Exception(
                            "Auth tag %s does not match expected value %s" %
                            (auth_tag, ex_mac))

                config.echo_json_response(self, 200, "Success")
                logger.info('PUT activated: ' + agent_id)
            elif "vactivate" in rest_params:
                deepquote = json_body.get('deepquote', None)
                try:
                    agent = session.query(RegistrarMain).filter_by(
                        agent_id=agent_id).first()
                except NoResultFound as e:
                    raise Exception(
                        "attempting to activate agent before requesting "
                        "registrar for %s" % agent_id) from e
                except SQLAlchemyError as e:
                    logger.error(f'SQLAlchemy Error: {e}')
                    raise

                if not agent['virtual']:
                    raise Exception(
                        "attempting to activate physical AIK using virtual interface for %s"
                        % agent_id)

                # get an physical AIK for this host
                registrar_client.init_client_tls('registrar')
                provider_keys = registrar_client.getKeys(
                    config.get('registrar', 'provider_registrar_ip'),
                    config.get('registrar', 'provider_registrar_tls_port'),
                    agent_id)
                # we already have the vaik
                tpm = tpm_obj.getTPM(need_hw_tpm=False,
                                     tpm_version=agent['tpm_version'])
                if not tpm.check_deep_quote(
                        agent_id,
                        hashlib.sha1(agent['key']).hexdigest(),
                        agent_id + agent['aik'] + agent['ek'], deepquote,
                        agent['aik'], provider_keys['aik']):
                    raise Exception("Deep quote invalid")
                try:
                    session.query(RegistrarMain).filter(
                        RegistrarMain.agent_id == agent_id).update(
                            {'active': True})
                except SQLAlchemyError as e:
                    logger.error(f'SQLAlchemy Error: {e}')
                    raise
                try:
                    session.query(RegistrarMain).filter(
                        RegistrarMain.agent_id == agent_id).update(
                            {'provider_keys': provider_keys})
                except SQLAlchemyError as e:
                    logger.error(f'SQLAlchemy Error: {e}')
                    raise

                config.echo_json_response(self, 200, "Success")
                logger.info('PUT activated: ' + agent_id)
        except Exception as e:
            config.echo_json_response(self, 400, "Error: %s" % e)
            logger.warning("PUT for " + agent_id +
                           " returning 400 response. Error: %s" % e)
            logger.exception(e)
            return
Exemple #15
0
    def do_PUT(self):
        """This method handles the PUT requests to add agents to the Registrar Server.

        Currently, only agents resources are available for PUTing, i.e. /agents. All other PUT uri's
        will return errors.
        """
        session = SessionManager().make_session(engine)
        rest_params = config.get_restful_params(self.path)
        if rest_params is None:
            config.echo_json_response(
                self, 405, "Not Implemented: Use /agents/ interface")
            return

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

        agent_id = rest_params["agents"]

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

        try:
            content_length = int(self.headers.get('Content-Length', 0))
            if content_length == 0:
                config.echo_json_response(self, 400,
                                          "Expected non zero content length")
                logger.warning(
                    'PUT 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)

            auth_tag = json_body['auth_tag']
            try:
                agent = session.query(RegistrarMain).filter_by(
                    agent_id=agent_id).first()
            except NoResultFound as e:
                raise Exception(
                    "attempting to activate agent before requesting "
                    "registrar for %s" % agent_id) from e
            except SQLAlchemyError as e:
                logger.error('SQLAlchemy Error: %s', e)
                raise

            if config.STUB_TPM:
                try:
                    session.query(RegistrarMain).filter(
                        RegistrarMain.agent_id == agent_id).update(
                            {'active': True})
                    session.commit()
                except SQLAlchemyError as e:
                    logger.error('SQLAlchemy Error: %s', e)
                    raise
            else:
                # TODO(kaifeng) Special handling should be removed
                if engine.dialect.name == "mysql":
                    agent.key = agent.key.encode('utf-8')

                ex_mac = crypto.do_hmac(agent.key, agent_id)
                if ex_mac == auth_tag:
                    try:
                        session.query(RegistrarMain).filter(
                            RegistrarMain.agent_id == agent_id).update(
                                {'active': True})
                        session.commit()
                    except SQLAlchemyError as e:
                        logger.error('SQLAlchemy Error: %s', e)
                        raise
                else:
                    raise Exception(
                        "Auth tag %s does not match expected value %s" %
                        (auth_tag, ex_mac))

            config.echo_json_response(self, 200, "Success")
            logger.info('PUT activated: %s', agent_id)
        except Exception as e:
            config.echo_json_response(self, 400, "Error: %s" % e)
            logger.warning("PUT for %s returning 400 response. Error: %s",
                           agent_id, e)
            logger.exception(e)
            return
Exemple #16
0
    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.
        """

        logger.info('GET invoked from %s with uri: %s', self.client_address,
                    self.path)
        rest_params = config.get_restful_params(self.path)
        if rest_params is None:
            config.echo_json_response(
                self, 405,
                "Not Implemented: Use /keys/ or /quotes/ interfaces")
            return

        if "keys" in rest_params and rest_params['keys'] == 'verify':
            if self.server.K is None:
                logger.info(
                    'GET key challenge returning 400 response. bootstrap key not available'
                )
                config.echo_json_response(self, 400,
                                          "Bootstrap key not yet available.")
                return
            challenge = rest_params['challenge']
            response = {}
            response['hmac'] = crypto.do_hmac(self.server.K, challenge)
            config.echo_json_response(self, 200, "Success", response)
            logger.info('GET key challenge returning 200 response.')

        # If agent pubkey requested
        elif "keys" in rest_params and rest_params["keys"] == "pubkey":
            response = {}
            response['pubkey'] = self.server.rsapublickey_exportable

            config.echo_json_response(self, 200, "Success", response)
            logger.info('GET pubkey returning 200 response.')
            return

        elif "quotes" in rest_params:
            nonce = rest_params['nonce']
            pcrmask = rest_params['mask'] if 'mask' in rest_params else None
            vpcrmask = rest_params['vmask'] if 'vmask' in rest_params else None

            # if the query is not messed up
            if nonce is None:
                logger.warning(
                    'GET quote returning 400 response. nonce not provided as an HTTP parameter in request'
                )
                config.echo_json_response(
                    self, 400,
                    "nonce not provided as an HTTP parameter in request")
                return

            # Sanitization assurance (for tpm.run() tasks below)
            if not (nonce.isalnum() and
                    (pcrmask is None or pcrmask.isalnum()) and
                    (vpcrmask is None or vpcrmask.isalnum())):
                logger.warning(
                    'GET quote returning 400 response. parameters should be strictly alphanumeric'
                )
                config.echo_json_response(
                    self, 400, "parameters should be strictly alphanumeric")
                return

            # identity quotes are always shallow
            hash_alg = tpm_instance.defaults['hash']
            if not tpm_instance.is_vtpm(
            ) or rest_params["quotes"] == 'identity':
                quote = tpm_instance.create_quote(
                    nonce, self.server.rsapublickey_exportable, pcrmask,
                    hash_alg)
                imaMask = pcrmask

            # Allow for a partial quote response (without pubkey)
            enc_alg = tpm_instance.defaults['encrypt']
            sign_alg = tpm_instance.defaults['sign']

            if "partial" in rest_params and (rest_params["partial"] is None
                                             or int(rest_params["partial"],
                                                    0) == 1):
                response = {
                    'quote': quote,
                    'hash_alg': hash_alg,
                    'enc_alg': enc_alg,
                    'sign_alg': sign_alg,
                }
            else:
                response = {
                    'quote': quote,
                    'hash_alg': hash_alg,
                    'enc_alg': enc_alg,
                    'sign_alg': sign_alg,
                    'pubkey': self.server.rsapublickey_exportable,
                }

            # return a measurement list if available
            if TPM_Utilities.check_mask(imaMask, config.IMA_PCR):
                if not os.path.exists(config.IMA_ML):
                    logger.warning("IMA measurement list not available: %s",
                                   config.IMA_ML)
                else:
                    with open(config.IMA_ML, 'r') as f:
                        ml = f.read()
                    response['ima_measurement_list'] = ml

            # similar to how IMA log retrievals are triggered by IMA_PCR, we trigger boot logs with MEASUREDBOOT_PCRs
            # other possibilities would include adding additional data to rest_params to trigger boot log retrievals
            # generally speaking, retrieving the 15Kbytes of a boot log does not seem significant compared to the
            # potential Mbytes of an IMA measurement list.
            if TPM_Utilities.check_mask(imaMask, config.MEASUREDBOOT_PCRS[0]):
                if not os.path.exists(config.MEASUREDBOOT_ML):
                    logger.warning("TPM2 event log not available: %s",
                                   config.MEASUREDBOOT_ML)
                else:
                    with open(config.MEASUREDBOOT_ML, 'rb') as f:
                        el = base64.b64encode(f.read())
                    response['mb_measurement_list'] = el

            config.echo_json_response(self, 200, "Success", response)
            logger.info('GET %s quote returning 200 response.',
                        rest_params["quotes"])
            return

        else:
            logger.warning('GET returning 400 response. uri not supported: %s',
                           self.path)
            config.echo_json_response(self, 400, "uri not supported")
            return
    def do_PUT(self):
        """This method handles the PUT requests to add agents to the Registrar Server.

        Currently, only agents resources are available for PUTing, i.e. /agents. All other PUT uri's
        will return errors.
        """
        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(
                'PUT 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(
                'PUT 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 not valid")
            logger.error("PUT 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(
                    'PUT 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)

            auth_tag = json_body['auth_tag']
            try:
                agent = session.query(RegistrarMain).filter_by(
                    agent_id=agent_id).first()
            except NoResultFound as e:
                raise Exception(
                    "attempting to activate agent before requesting "
                    "registrar for %s" % agent_id) from e
            except SQLAlchemyError as e:
                logger.error('SQLAlchemy Error: %s', e)
                raise

            if config.STUB_TPM:
                try:
                    session.query(RegistrarMain).filter(
                        RegistrarMain.agent_id == agent_id).update(
                            {'active': int(True)})
                    session.commit()
                except SQLAlchemyError as e:
                    logger.error('SQLAlchemy Error: %s', e)
                    raise
            else:
                ex_mac = crypto.do_hmac(agent.key.encode(), agent_id)
                if ex_mac == auth_tag:
                    try:
                        session.query(RegistrarMain).filter(
                            RegistrarMain.agent_id == agent_id).update(
                                {'active': int(True)})
                        session.commit()
                    except SQLAlchemyError as e:
                        logger.error('SQLAlchemy Error: %s', e)
                        raise
                else:
                    raise Exception(
                        f"Auth tag {auth_tag} does not match expected value {ex_mac}"
                    )

            web_util.echo_json_response(self, 200, "Success")
            logger.info('PUT activated: %s', agent_id)
        except Exception as e:
            web_util.echo_json_response(self, 400, "Error: %s" % e)
            logger.warning("PUT for %s returning 400 response. Error: %s",
                           agent_id, e)
            logger.exception(e)
            return
    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.
        """

        logger.info('GET invoked from ' + str(self.client_address) +
                    ' with uri:' + self.path)
        logger.debug('THREAD ID' + threading.currentThread().getName())

        rest_params = common.get_restful_params(self.path)
        if rest_params is None:
            common.echo_json_response(
                self, 405,
                "Not Implemented: Use /keys/ or /quotes/ interfaces")
            return

        if "keys" in rest_params and rest_params['keys'] == 'verify':
            if self.server.K is None:
                logger.info(
                    'GET key challenge returning 400 response. bootstrap key not available'
                )
                common.echo_json_response(self, 400,
                                          "Bootstrap key not yet available.")
                return
            challenge = rest_params['challenge']
            response = {}
            response['hmac'] = crypto.do_hmac(self.server.K, challenge)
            common.echo_json_response(self, 200, "Success", response)
            logger.info('GET key challenge returning 200 response.')

        # If agent pubkey requested
        elif "keys" in rest_params and rest_params["keys"] == "pubkey":
            response = {}
            response['pubkey'] = self.server.rsapublickey_exportable

            common.echo_json_response(self, 200, "Success", response)
            logger.info('GET pubkey returning 200 response.')
            return

        elif "quotes" in rest_params:
            nonce = rest_params['nonce']
            pcrmask = rest_params['mask'] if 'mask' in rest_params else None
            vpcrmask = rest_params['vmask'] if 'vmask' in rest_params else None

            # if the query is not messed up
            if nonce is None:
                logger.warning(
                    'GET quote returning 400 response. nonce not provided as an HTTP parameter in request'
                )
                common.echo_json_response(
                    self, 400,
                    "nonce not provided as an HTTP parameter in request")
                return

            # Sanitization assurance (for tpm.run() tasks below)
            if not (nonce.isalnum() and
                    (pcrmask is None or pcrmask.isalnum()) and
                    (vpcrmask is None or vpcrmask.isalnum())):
                logger.warning(
                    'GET quote returning 400 response. parameters should be strictly alphanumeric'
                )
                common.echo_json_response(
                    self, 400, "parameters should be strictly alphanumeric")
                return

            # identity quotes are always shallow
            hash_alg = tpm.defaults['hash']
            if not tpm.is_vtpm() or rest_params["quotes"] == 'identity':
                quote = tpm.create_quote(nonce,
                                         self.server.rsapublickey_exportable,
                                         pcrmask, hash_alg)
                imaMask = pcrmask
            else:
                quote = tpm.create_deep_quote(
                    nonce, self.server.rsapublickey_exportable, vpcrmask,
                    pcrmask)
                imaMask = vpcrmask

            # Allow for a partial quote response (without pubkey)
            enc_alg = tpm.defaults['encrypt']
            sign_alg = tpm.defaults['sign']
            if "partial" in rest_params and (rest_params["partial"] is None
                                             or int(rest_params["partial"],
                                                    0) == 1):
                response = {
                    'quote': quote,
                    'tpm_version': tpm_version,
                    'hash_alg': hash_alg,
                    'enc_alg': enc_alg,
                    'sign_alg': sign_alg,
                }
            else:
                response = {
                    'quote': quote,
                    'tpm_version': tpm_version,
                    'hash_alg': hash_alg,
                    'enc_alg': enc_alg,
                    'sign_alg': sign_alg,
                    'pubkey': self.server.rsapublickey_exportable,
                }

            # return a measurement list if available
            if TPM_Utilities.check_mask(imaMask, common.IMA_PCR):
                if not os.path.exists(common.IMA_ML):
                    logger.warn("IMA measurement list not available: %s" %
                                (common.IMA_ML))
                else:
                    with open(common.IMA_ML, 'r') as f:
                        ml = f.read()
                    response['ima_measurement_list'] = ml

            #time.sleep(5)
            common.echo_json_response(self, 200, "Success", response)
            logger.info('GET %s quote returning 200 response.' %
                        (rest_params["quotes"]))
            return

        else:
            logger.warning('GET returning 400 response. uri not supported: ' +
                           self.path)
            common.echo_json_response(self, 400, "uri not supported")
            return
    def do_PUT(self):
        """This method handles the PUT requests to add agents to the Registrar Server.

        Currently, only agents resources are available for PUTing, i.e. /agents. All other PUT uri's
        will return errors.
        """
        rest_params = common.get_restful_params(self.path)
        if rest_params is None:
            common.echo_json_response(
                self, 405, "Not Implemented: Use /agents/ interface")
            return

        if "agents" not in rest_params:
            common.echo_json_response(self, 400, "uri not supported")
            logger.warning(
                'PUT agent returning 400 response. uri not supported: ' +
                self.path)
            return

        agent_id = rest_params["agents"]

        if agent_id is None:
            common.echo_json_response(self, 400, "agent id not found in uri")
            logger.warning(
                'PUT agent returning 400 response. agent id not found in uri '
                + self.path)
            return

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

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

            if "activate" in rest_params:
                auth_tag = json_body['auth_tag']

                agent = self.server.db.get_agent(agent_id)
                if agent is None:
                    raise Exception(
                        "attempting to activate agent before requesting registrar for %s"
                        % agent_id)

                if agent['virtual']:
                    raise Exception(
                        "attempting to activate virtual AIK using physical interface for %s"
                        % agent_id)

                if common.STUB_TPM:
                    self.server.db.update_agent(agent_id, 'active', True)
                else:
                    ex_mac = crypto.do_hmac(agent['key'], agent_id)
                    if ex_mac == auth_tag:
                        self.server.db.update_agent(agent_id, 'active', True)
                    else:
                        raise Exception(
                            "Auth tag %s does not match expected value %s" %
                            (auth_tag, ex_mac))

                common.echo_json_response(self, 200, "Success")
                logger.info('PUT activated: ' + agent_id)
            elif "vactivate" in rest_params:
                deepquote = json_body.get('deepquote', None)

                agent = self.server.db.get_agent(agent_id)
                if agent is None:
                    raise Exception(
                        "attempting to activate agent before requesting registrar for %s"
                        % agent_id)

                if not agent['virtual']:
                    raise Exception(
                        "attempting to activate physical AIK using virtual interface for %s"
                        % agent_id)

                # get an physical AIK for this host
                registrar_client.init_client_tls(config, 'registrar')
                provider_keys = registrar_client.getKeys(
                    config.get('general', 'provider_registrar_ip'),
                    config.get('general', 'provider_registrar_tls_port'),
                    agent_id)
                # we already have the vaik
                tpm = tpm_obj.getTPM(need_hw_tpm=False,
                                     tpm_version=agent['tpm_version'])
                if not tpm.check_deep_quote(
                        hashlib.sha1(agent['key']).hexdigest(),
                        agent_id + agent['aik'] + agent['ek'], deepquote,
                        agent['aik'], provider_keys['aik']):
                    raise Exception("Deep quote invalid")

                self.server.db.update_agent(agent_id, 'active', True)
                self.server.db.update_agent(agent_id, 'provider_keys',
                                            provider_keys)

                common.echo_json_response(self, 200, "Success")
                logger.info('PUT activated: ' + agent_id)
            else:
                pass
        except Exception as e:
            common.echo_json_response(self, 400, "Error: %s" % e)
            logger.warning("PUT for " + agent_id +
                           " returning 400 response. Error: %s" % e)
            logger.exception(e)
            return
Exemple #20
0
    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.
        """

        logger.info("GET invoked from %s with uri: %s", self.client_address,
                    self.path)
        rest_params = web_util.get_restful_params(self.path)
        if rest_params is None:
            web_util.echo_json_response(
                self, 405,
                "Not Implemented: Use /version, /keys/ or /quotes/ interfaces")
            return

        if "version" in rest_params:
            version_info = {
                "supported_version": keylime_api_version.current_version()
            }
            web_util.echo_json_response(self, 200, "Success", version_info)
            return

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

        if "keys" in rest_params and rest_params["keys"] == "verify":
            if self.server.K is None:
                logger.info(
                    "GET key challenge returning 400 response. bootstrap key not available"
                )
                web_util.echo_json_response(
                    self, 400, "Bootstrap key not yet available.")
                return
            if "challenge" not in rest_params:
                logger.info(
                    "GET key challenge returning 400 response. No challenge provided"
                )
                web_util.echo_json_response(self, 400,
                                            "No challenge provided.")
                return

            challenge = rest_params["challenge"]
            response = {}
            response["hmac"] = crypto.do_hmac(self.server.K, challenge)
            web_util.echo_json_response(self, 200, "Success", response)
            logger.info("GET key challenge returning 200 response.")

        # If agent pubkey requested
        elif "keys" in rest_params and rest_params["keys"] == "pubkey":
            response = {}
            response["pubkey"] = self.server.rsapublickey_exportable

            web_util.echo_json_response(self, 200, "Success", response)
            logger.info("GET pubkey returning 200 response.")
            return

        elif "quotes" in rest_params:
            nonce = rest_params.get("nonce", None)
            pcrmask = rest_params.get("mask", None)
            ima_ml_entry = rest_params.get("ima_ml_entry", "0")

            # if the query is not messed up
            if nonce is None:
                logger.warning(
                    "GET quote returning 400 response. nonce not provided as an HTTP parameter in request"
                )
                web_util.echo_json_response(
                    self, 400,
                    "nonce not provided as an HTTP parameter in request")
                return

            # Sanitization assurance (for tpm.run() tasks below)
            if not (nonce.isalnum() and
                    (pcrmask is None or validators.valid_hex(pcrmask))
                    and ima_ml_entry.isalnum()):
                logger.warning(
                    "GET quote returning 400 response. parameters should be strictly alphanumeric"
                )
                web_util.echo_json_response(
                    self, 400, "parameters should be strictly alphanumeric")
                return

            if len(nonce) > tpm_instance.MAX_NONCE_SIZE:
                logger.warning(
                    "GET quote returning 400 response. Nonce is too long (max size %i): %i",
                    tpm_instance.MAX_NONCE_SIZE,
                    len(nonce),
                )
                web_util.echo_json_response(
                    self, 400,
                    f"Nonce is too long (max size {tpm_instance.MAX_NONCE_SIZE}): {len(nonce)}"
                )
                return

            hash_alg = tpm_instance.defaults["hash"]
            quote = tpm_instance.create_quote(
                nonce, self.server.rsapublickey_exportable, pcrmask, hash_alg)
            imaMask = pcrmask

            # Allow for a partial quote response (without pubkey)
            enc_alg = tpm_instance.defaults["encrypt"]
            sign_alg = tpm_instance.defaults["sign"]

            if "partial" in rest_params and (rest_params["partial"] is None
                                             or rest_params["partial"] == "1"):
                response = {
                    "quote": quote,
                    "hash_alg": hash_alg,
                    "enc_alg": enc_alg,
                    "sign_alg": sign_alg,
                }
            else:
                response = {
                    "quote": quote,
                    "hash_alg": hash_alg,
                    "enc_alg": enc_alg,
                    "sign_alg": sign_alg,
                    "pubkey": self.server.rsapublickey_exportable,
                }

            response["boottime"] = self.server.boottime

            # return a measurement list if available
            if TPM_Utilities.check_mask(imaMask, config.IMA_PCR):
                ima_ml_entry = int(ima_ml_entry)
                if ima_ml_entry > self.server.next_ima_ml_entry:
                    ima_ml_entry = 0
                ml, nth_entry, num_entries = ima.read_measurement_list(
                    self.server.ima_log_file, ima_ml_entry)
                if num_entries > 0:
                    response["ima_measurement_list"] = ml
                    response["ima_measurement_list_entry"] = nth_entry
                    self.server.next_ima_ml_entry = num_entries

            # similar to how IMA log retrievals are triggered by IMA_PCR, we trigger boot logs with MEASUREDBOOT_PCRs
            # other possibilities would include adding additional data to rest_params to trigger boot log retrievals
            # generally speaking, retrieving the 15Kbytes of a boot log does not seem significant compared to the
            # potential Mbytes of an IMA measurement list.
            if TPM_Utilities.check_mask(imaMask, config.MEASUREDBOOT_PCRS[0]):
                if not self.server.tpm_log_file_data:
                    logger.warning("TPM2 event log not available: %s",
                                   config.MEASUREDBOOT_ML)
                else:
                    response[
                        "mb_measurement_list"] = self.server.tpm_log_file_data

            web_util.echo_json_response(self, 200, "Success", response)
            logger.info("GET %s quote returning 200 response.",
                        rest_params["quotes"])
            return

        else:
            logger.warning("GET returning 400 response. uri not supported: %s",
                           self.path)
            web_util.echo_json_response(self, 400, "uri not supported")
            return
Exemple #21
0
 def test_hmac(self):
     message = "a secret message!"
     aeskey = kdf(message, "salty-McSaltface")
     digest = do_hmac(aeskey, message)
     aeskey2 = kdf(message, "salty-McSaltface")
     self.assertEqual(do_hmac(aeskey2, message), digest)