def test_011_reg_instance_activate_put(self): """Test registrar's PUT /v2/instances/{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_initialize.activate_identity(keyblob) data = { 'auth_tag': crypto.do_hmac(base64.b64decode(key), tenant_templ.node_uuid), } v_json_message = json.dumps(data) response = tornado_requests.request( "PUT", "http://%s:%s/v2/instances/%s/activate" % (tenant_templ.registrar_ip, tenant_templ.registrar_boot_port, tenant_templ.node_uuid), data=v_json_message, context=None) self.assertEqual( response.status_code, 200, "Non-successful Registrar Instance Activate return code!") response_body = response.json() # Ensure response is well-formed self.assertIn("results", response_body, "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 = str(crypto.strbitxor(decrypted_U, decrypted_V)) # be very careful printing K, U, or V as they leak in logs stored on unprotected disks if common.INSECURE_DEBUG: logger.debug("U: " + base64.b64encode(decrypted_U)) logger.debug("V: " + base64.b64encode(decrypted_V)) logger.debug("K: " + base64.b64encode(candidate_key)) logger.debug( "auth_tag: " + 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 return False
def test_041_node_keys_verify_get(self): """Test node's GET /v2/keys/verify Interface""" self.assertIsNotNone( self.K, "Required value not set. Previous step may have failed?") challenge = tpm_initialize.random_password(20) response = tornado_requests.request( "GET", "http://%s:%s/v2/keys/verify/challenge/%s/" % (tenant_templ.cloudnode_ip, tenant_templ.cloudnode_port, challenge)) self.assertEqual(response.status_code, 200, "Non-successful Node verify return code!") response_body = response.json() # Ensure response is well-formed self.assertIn("results", response_body, "Malformed response body!") self.assertIn("hmac", response_body["results"], "Malformed response body!") # Be sure response is valid mac = response_body['results']['hmac'] ex_mac = crypto.do_hmac(self.K, challenge) self.assertEqual(mac, ex_mac, "Node failed to validate challenge code!")
def preloop(self): # encrypt the node UUID as a check for delivering the correct key self.auth_tag = crypto.do_hmac(self.K, self.node_uuid) # be very careful printing K, U, or V as they leak in logs stored on unprotected disks if common.DEVELOP_IN_ECLIPSE: logger.debug("K:" + base64.b64encode(self.K)) logger.debug("V:" + base64.b64encode(self.V)) logger.debug("U:" + base64.b64encode(str(self.U))) logger.debug("Auth Tag: " + self.auth_tag)
def do_verify(self): """initiaite v, instance_id and ip initiate the cloudinit sequence""" challenge = tpm_initialize.random_password(20) numtries = 0 while True: try: response = tornado_requests.request( "GET", "http://%s:%s/v2/keys/verify/challenge/%s/" % (self.cloudnode_ip, self.cloudnode_port, challenge)) except Exception as e: # this is one exception that should return a 'keep going' response if tornado_requests.is_refused(e): numtries += 1 maxr = config.getint('tenant', 'max_retries') if numtries >= maxr: logger.error( "Quitting after max number of retries to connect to %s" % (self.cloudnode_ip)) raise e retry = config.getfloat('tenant', 'retry_interval') logger.info( "Connection to %s refused %d/%d times, trying again in %f seconds..." % (self.cloudnode_ip, numtries, maxr, retry)) time.sleep(retry) continue else: 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( "Error: unexpected http response body from Cloud Node: %s" % str(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: common.log_http_response(logger, logging.ERROR, response_body) retry = config.getfloat('tenant', 'retry_interval') logger.error( "Key derivation not yet complete...trying again in %s seconds...Ctrl-C to stop" % retry) time.sleep(retry) continue break
def setUpClass(cls): """Prepare the keys and payload to give to the CV""" contents = "random garbage to test as payload" 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 a node""" cls.auth_tag = crypto.do_hmac(cls.K, tenant_templ.node_uuid) """Prepare policies for node""" cls.tpm_policy = config.get('tenant', 'tpm_policy') cls.vtpm_policy = config.get('tenant', 'vtpm_policy') cls.tpm_policy = tpm_quote.readPolicy(cls.tpm_policy) cls.vtpm_policy = tpm_quote.readPolicy(cls.vtpm_policy)
def doActivateNode(registrar_ip,registrar_port,instance_id,key): global context data = { 'command': 'activate_node', 'auth_tag': crypto.do_hmac(base64.b64decode(key),instance_id), } v_json_message = json.dumps(data) response = tornado_requests.request("PUT", "http://%s:%s/v1/instance_id/%s"%(registrar_ip,registrar_port,instance_id), data=v_json_message, context=context) if response.status_code == 200: logger.info("Registration activated for node %s."%instance_id) else: logger.error("Error: unexpected http response code from Registrar Server: " + str(response.status_code))
def doActivateNode(registrar_ip,registrar_port,instance_id,key): data = { 'auth_tag': crypto.do_hmac(base64.b64decode(key),instance_id), } v_json_message = json.dumps(data) response = tornado_requests.request("PUT", "http://%s:%s/v2/instances/%s/activate"%(registrar_ip,registrar_port,instance_id), data=v_json_message, context=None) if response.status_code == 200: logger.info("Registration activated for node %s."%instance_id) return True else: logger.error("Error: unexpected http response code from Registrar Server: " + str(response.status_code)) common.log_http_response(logger,logging.ERROR,response.json()) return False
def setUpClass(cls): """Prepare the keys and payload to give to the CV""" contents = "random garbage to test as payload" 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_Utilities.readPolicy(cls.tpm_policy) cls.vtpm_policy = TPM_Utilities.readPolicy(cls.vtpm_policy) """Allow targeting a specific API version (default latest)""" cls.api_version = common.API_VERSION
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) 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 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(base64.b64decode(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_PUT(self): """This method handles the POST requests to add instances to the Registrar Server. Currently, only instances resources are available for POSTing, i.e. /v1/instances. All other POST uri's will return errors. PUT requests require an an instance_id identifying the instance to add, and json block sent in the body with 2 entries: ek and aik. """ if not self.is_instance_resource(): self.send_response(400) self.end_headers() logger.warning('PUT instance returning 400 response. uri not supported: ' + self.path) return instance_id = self.get_resource_name() if instance_id is None: self.send_response(400) self.end_headers() logger.warning('PUT instance returning 400 response. instance_id not found in uri ' + self.path) return try: content_length = int(self.headers.get('Content-Length', 0)) if content_length == 0: self.send_response(400) self.end_headers() logger.warning('PUT for ' + instance_id + ' returning 400 response. Expected non zero content length.') return post_body = self.rfile.read(content_length) json_body = json.loads(post_body) command = json_body['command'] if command=='register_node': ek = json_body['ek'] ekcert = json_body['ekcert'] aik = json_body['aik'] # config option must be on to check for EK certs if config.getboolean('registrar','require_ek_cert'): # no EK provided if ekcert is None and not common.DEVELOP_IN_ECLIPSE: raise Exception("No EK cert provided, require_ek_cert option in config set to True") # there is an EK if not common.STUB_TPM and (ekcert!=None and ekcert!='virtual' and not tpm_initialize.verify_ek(base64.b64decode(ekcert), ek)): raise Exception("Invalid EK certificate") # try to encrypt the AIK (blob,key) = tpm_initialize.encryptAIK(instance_id,aik,ek) self.server.add_instance(instance_id, key, aik,ek,ekcert) response = { 'blob': blob, } json_response = json.dumps(response) self.send_response(200) self.end_headers() self.wfile.write(json_response) logger.info('PUT returning key blob for instance_id: ' + instance_id) return elif command=='activate_node': auth_tag=json_body['auth_tag'] instance = self.server.find_instance(instance_id) if instance is None: raise Exception("attempting to activate instance before requesting registrar for %s"%instance_id) if instance['virtual']: raise Exception("attempting to activate virtual AIK using physical interface for %s"%instance_id) if common.STUB_TPM: self.server.update_instance(instance_id, 'active',True) else: ex_mac = crypto.do_hmac(base64.b64decode(instance['key']),instance_id) if ex_mac == auth_tag: self.server.update_instance(instance_id, 'active',True) else: raise Exception("Auth tag %s does not match expected value %s"%(auth_tag,ex_mac)) self.send_response(200) self.end_headers() logger.info('PUT activated: ' + instance_id) elif command=='activate_virtual_node': deepquote = json_body.get('deepquote',None) instance = self.server.find_instance(instance_id) if instance is None: raise Exception("attempting to activate instance before requesting registrar for %s"%instance_id) if not instance['virtual']: raise Exception("attempting to activate physical AIK using virtual interface for %s"%instance_id) # get an physical AIK for this host registrar_client.serverAuthTLSContext(config, 'registrar') dq_aik = registrar_client.getAIK(config.get('general', 'provider_registrar_ip'), config.get('general', 'provider_registrar_port'), instance_id) # we already have the vaik if not tpm_quote.check_deep_quote(hashlib.sha1(instance['key']), instance_id+instance['aik']+instance['ek'], deepquote, instance['aik'], dq_aik): raise Exception("Deep quote invalid") self.server.update_instance(instance_id, 'active',True) self.send_response(200) self.end_headers() logger.info('PUT activated: ' + instance_id) else: pass except Exception as e: self.send_response(400) self.end_headers() logger.warning("PUT for " + instance_id + " returning 400 response. Error: %s"%e) logger.warning(traceback.format_exc()) return