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!")
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!")
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"] = ["*"]
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!")
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
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}")
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
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)
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
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
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
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
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
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
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
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
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)