def setUpModule(): try: env = os.environ.copy() env['PATH']=env['PATH']+":/usr/local/bin" # Run init_tpm_server and tpm_serverd (start fresh) its = subprocess.Popen(["init_tpm_server"], shell=False, env=env) its.wait() tsd = subprocess.Popen(["tpm_serverd"], shell=False, env=env) tsd.wait() except Exception as e: print("WARNING: Restarting TPM emulator failed!") # Note: the following is required as abrmd is failing to reconnect to MSSIM, once # MSSIM is killed and restarted. If this is an proved an actual bug and is # fixed upstream, the following dbus restart call can be removed. try: sysbus = dbus.SystemBus() systemd1 = sysbus.get_object('org.freedesktop.systemd1', '/org/freedesktop/systemd1') manager = dbus.Interface(systemd1, 'org.freedesktop.systemd1.Manager') # If the systemd service exists, let's restart it. for service in sysbus.list_names(): if "com.intel.tss2.Tabrmd" in service: print("Found dbus service:", str(service)) try: print("Restarting tpm2-abrmd.service.") job = manager.RestartUnit('tpm2-abrmd.service', 'fail') except dbus.exceptions.DBusException as e: print(e) except Exception as e: print("Non systemd agent detected, no tpm2-abrmd restart required.") try: # Start with a clean slate for this test fileRemove(common.WORK_DIR + "/tpmdata.yaml") fileRemove(common.WORK_DIR + "/cv_data.sqlite") fileRemove(common.WORK_DIR + "/reg_data.sqlite") shutil.rmtree(common.WORK_DIR + "/cv_ca", True) except Exception as e: print("WARNING: Cleanup of TPM files failed!") # CV must be run first to create CA and certs! launch_cloudverifier() launch_registrar() #launch_cloudagent() # get the tpm object global tpm try: tpm = tpm_obj.getTPM(need_hw_tpm=True) except Exception as e: print("Error: %s" % e) # Make the Tenant do a lot of set-up work for us global tenant_templ tenant_templ = tenant.Tenant() tenant_templ.cloudagent_ip = "localhost" tenant_templ.agent_uuid = config.get('cloud_agent', 'agent_uuid') tenant_templ.registrar_boot_port = config.get('registrar', 'registrar_port') tenant_templ.registrar_tls_boot_port = config.get('registrar', 'registrar_tls_port')
def process_quote_response(agent, json_response): """Validates the response from the Cloud agent. This method invokes an Registrar Server call to register, and then check the quote. """ received_public_key = None quote = None # in case of failure in response content do not continue try: received_public_key = json_response.get("pubkey", None) quote = json_response["quote"] ima_measurement_list = json_response.get("ima_measurement_list", None) logger.debug("received quote: %s" % quote) logger.debug("for nonce: %s" % agent['nonce']) logger.debug("received public key: %s" % received_public_key) logger.debug("received ima_measurement_list %s" % (ima_measurement_list is not None)) except Exception: return None # if no public key provided, then ensure we have cached it if received_public_key is None: if agent.get('public_key', "") == "" or agent.get( 'b64_encrypted_V', "") == "": logger.error( "agent did not provide public key and no key or encrypted_v was cached at CV" ) return False agent['provide_V'] = False received_public_key = agent['public_key'] if agent.get('registrar_keys', "") == "": registrar_client.init_client_tls('cloud_verifier') registrar_keys = registrar_client.getKeys( config.get("cloud_verifier", "registrar_ip"), config.get("cloud_verifier", "registrar_port"), agent['agent_id']) if registrar_keys is None: logger.warning("AIK not found in registrar, quote not validated") return False agent['registrar_keys'] = registrar_keys tpm_version = json_response.get('tpm_version') tpm = tpm_obj.getTPM(need_hw_tpm=False, tpm_version=tpm_version) hash_alg = json_response.get('hash_alg') enc_alg = json_response.get('enc_alg') sign_alg = json_response.get('sign_alg') # Update chosen tpm and algorithms agent['tpm_version'] = tpm_version agent['hash_alg'] = hash_alg agent['enc_alg'] = enc_alg agent['sign_alg'] = sign_alg # Ensure hash_alg is in accept_tpm_hash_alg list if not algorithms.is_accepted(hash_alg, agent['accept_tpm_hash_algs']): raise Exception("TPM Quote is using an unaccepted hash algorithm: %s" % hash_alg) # Ensure enc_alg is in accept_tpm_encryption_algs list if not algorithms.is_accepted(enc_alg, agent['accept_tpm_encryption_algs']): raise Exception( "TPM Quote is using an unaccepted encryption algorithm: %s" % enc_alg) # Ensure sign_alg is in accept_tpm_encryption_algs list if not algorithms.is_accepted(sign_alg, agent['accept_tpm_signing_algs']): raise Exception( "TPM Quote is using an unaccepted signing algorithm: %s" % sign_alg) if tpm.is_deep_quote(quote): validQuote = tpm.check_deep_quote( agent['agent_id'], agent['nonce'], received_public_key, quote, agent['registrar_keys']['aik'], agent['registrar_keys']['provider_keys']['aik'], agent['vtpm_policy'], agent['tpm_policy'], ima_measurement_list, agent['allowlist']) else: validQuote = tpm.check_quote(agent['agent_id'], agent['nonce'], received_public_key, quote, agent['registrar_keys']['aik'], agent['tpm_policy'], ima_measurement_list, agent['allowlist'], hash_alg) if not validQuote: return False # set a flag so that we know that the agent was verified once. # we only issue notifications for agents that were at some point good agent['first_verified'] = True # has public key changed? if so, clear out b64_encrypted_V, it is no longer valid if received_public_key != agent.get('public_key', ""): agent['public_key'] = received_public_key agent['b64_encrypted_V'] = "" agent['provide_V'] = True # ok we're done return validQuote
import tempfile from uuid import UUID import simplejson as json try: from yaml import CSafeDumper as SafeDumper except ImportError: from yaml import SafeDumper from keylime import config from keylime import keylime_logging from keylime.tpm import tpm_obj # get the tpm object tpm = tpm_obj.getTPM(need_hw_tpm=True) sys.path.append(os.path.dirname(__file__)) # Logging boiler plate logger = keylime_logging.init_logging('vtpmmgr') logger.setLevel(logging.INFO) # ./utils/encaik -ek ~/tmp/LLSRC-tci/scripts/llsrc-vtpm-host0_pubek.pem -ik ~/tmp/LLSRC-tci/scripts/llsrc-vtpm-host0_pubek.pem -ok key.blob -oak key.aes # cd /home/rudd/tmp/tpm4720/libtpm # VTPM Command Ordinals. Taken from Xen's stubdoms/vtpmmgr/vtpm_manager.h VTPM_ORD_GROUP_LIST = 0x02000101 VTPM_ORD_GROUP_NEW = 0x02000102 VTPM_ORD_GROUP_DEL = 0x02000103 VTPM_ORD_GROUP_ACTIVATE = 0x02000104
def main(argv=sys.argv): parser = argparse.ArgumentParser(argv[0]) parser.add_argument( '-c', '--command', action='store', dest='command', default='add', help= "valid commands are add,delete,update,status,list,reactivate,regdelete. defaults to add" ) parser.add_argument('-t', '--targethost', action='store', dest='agent_ip', help="the IP address of the host to provision") parser.add_argument('-tp', '--targetport', action='store', dest='agent_port', help="the Port of the host to provision") parser.add_argument( '--cv_targethost', action='store', default=None, dest='cv_agent_ip', help= 'the IP address of the host to provision that the verifier will use (optional). Use only if different than argument to option -t/--targethost' ) parser.add_argument('-v', '--cv', action='store', dest='verifier_ip', help="the IP address of the cloud verifier") parser.add_argument('-u', '--uuid', action='store', dest='agent_uuid', help="UUID for the agent to provision") parser.add_argument( '-f', '--file', action='store', default=None, help='Deliver the specified plaintext to the provisioned agent') parser.add_argument( '--cert', action='store', dest='ca_dir', default=None, help= 'Create and deliver a certificate using a CA created by ca-util. Pass in the CA directory or use "default" to use the standard dir' ) parser.add_argument( '-k', '--key', action='store', dest='keyfile', help='an intermedia key file produced by user_data_encrypt') parser.add_argument( '-p', '--payload', action='store', default=None, help= 'Specify the encrypted payload to deliver with encrypted keys specified by -k' ) parser.add_argument( '--include', action='store', dest='incl_dir', default=None, help= "Include additional files in provided directory in certificate zip file. Must be specified with --cert" ) parser.add_argument('--whitelist', action='store', dest='ima_whitelist', default=None, help="Specify the location of an IMA whitelist") parser.add_argument('--exclude', action='store', dest='ima_exclude', default=None, help="Specify the location of an IMA exclude list") parser.add_argument( '--tpm_policy', action='store', dest='tpm_policy', default=None, help= "Specify a TPM policy in JSON format. e.g., {\"15\":\"0000000000000000000000000000000000000000\"}" ) parser.add_argument('--vtpm_policy', action='store', dest='vtpm_policy', default=None, help="Specify a vTPM policy in JSON format") parser.add_argument( '--verify', action='store_true', default=False, help= 'Block on cryptographically checked key derivation confirmation from the agent once it has been provisioned' ) if common.DEVELOP_IN_ECLIPSE and len(argv) == 1: ca_util.setpassword('default') #tmp = ['-c','add','-t','127.0.0.1','-v', '127.0.0.1','-u','C432FBB3-D2F1-4A97-9EF7-75BD81C866E9','-p','content_payload.txt','-k','content_keys.txt'] #tmp = ['-c','add','-t','127.0.0.1','-v','127.0.0.1','-u','C432FBB3-D2F1-4A97-9EF7-75BD81C866E9','-f','tenant.py'] tmp = [ '-c', 'add', '-t', '127.0.0.1', '-v', '127.0.0.1', '-u', 'C432FBB3-D2F1-4A97-9EF7-75BD81C866E9', '--cert', 'ca/' ] #tmp = ['-c','delete','-t','127.0.0.1','-v','127.0.0.1','-u','C432FBB3-D2F1-4A97-9EF7-75BD81C866E9'] #tmp = ['-c','reactivate','-t','127.0.0.1','-v','127.0.0.1','-u','C432FBB3-D2F1-4A97-9EF7-75BD81C866E9'] #tmp = ['-c','list','-v', '127.0.0.1','-u','C432FBB3-D2F1-4A97-9EF7-75BD81C866E9'] #tmp = ['-c','regdelete','-u','C432FBB3-D2F1-4A97-9EF7-75BD81C866E9'] else: tmp = argv[1:] args = parser.parse_args(tmp) mytenant = Tenant() if args.command not in ['list', 'regdelete', 'delete' ] and args.agent_ip is None: raise UserError( f"-t/--targethost is required for command {args.command}") if args.agent_uuid is not None: mytenant.agent_uuid = args.agent_uuid # if the uuid is actually a public key, then hash it if mytenant.agent_uuid.startswith('-----BEGIN PUBLIC KEY-----'): mytenant.agent_uuid = hashlib.sha256( mytenant.agent_uuid).hexdigest() else: logger.warning( "Using default UUID D432FBB3-D2F1-4A97-9EF7-75BD81C00000") mytenant.agent_uuid = "D432FBB3-D2F1-4A97-9EF7-75BD81C00000" if common.STUB_VTPM and common.TPM_CANNED_VALUES is not None: # Use canned values for agent UUID jsonIn = common.TPM_CANNED_VALUES if "add_vtpm_to_group" in jsonIn: mytenant.agent_uuid = jsonIn['add_vtpm_to_group']['retout'] else: # Our command hasn't been canned! raise UserError("Command %s not found in canned JSON!" % ("add_vtpm_to_group")) if args.verifier_ip is not None: mytenant.cloudverifier_ip = args.verifier_ip if args.command == 'add': mytenant.init_add(vars(args)) mytenant.preloop() mytenant.do_cv() mytenant.do_quote() if args.verify: mytenant.do_verify() if common.DEVELOP_IN_ECLIPSE: time.sleep(2) mytenant.do_cvstatus() time.sleep(1) # invalidate it eventually logger.debug("invalidating PCR 15, forcing revocation") tpm = tpm_obj.getTPM(need_hw_tpm=True) tpm.extendPCR(15, tpm.hashdigest(b"garbage")) time.sleep(5) logger.debug("Deleting agent from verifier") mytenant.do_cvdelete() elif args.command == 'update': mytenant.init_add(vars(args)) mytenant.do_cvdelete() mytenant.preloop() mytenant.do_cv() mytenant.do_quote() if args.verify: mytenant.do_verify() elif args.command == 'delete': mytenant.do_cvdelete() elif args.command == 'status': mytenant.do_cvstatus() elif args.command == 'list': mytenant.do_cvstatus(listing=True) elif args.command == 'reactivate': mytenant.do_cvreactivate() elif args.command == 'regdelete': mytenant.do_regdelete() else: raise UserError("Invalid command specified: %s" % (args.command))
def validate_tpm_quote(self, public_key, quote, tpm_version, hash_alg): registrar_client.init_client_tls(config, 'tenant') reg_keys = registrar_client.getKeys(self.registrar_ip, self.registrar_port, self.agent_uuid) if reg_keys is None: logger.warning("AIK not found in registrar, quote not validated") return False tpm = tpm_obj.getTPM(need_hw_tpm=False, tpm_version=tpm_version) if not tpm.check_quote(self.agent_uuid, self.nonce, public_key, quote, reg_keys['aik'], hash_alg=hash_alg): if reg_keys['regcount'] > 1: logger.error( "WARNING: This UUID had more than one ek-ekcert registered to it! This might indicate that your system is misconfigured or a malicious host is present. Run 'regdelete' for this agent and restart" ) exit() return False if reg_keys['regcount'] > 1: logger.warn( "WARNING: This UUID had more than one ek-ekcert registered to it! This might indicate that your system is misconfigured. Run 'regdelete' for this agent and restart" ) if not common.STUB_TPM and ( not config.getboolean('tenant', 'require_ek_cert') and config.get('tenant', 'ek_check_script') == ""): logger.warn( "DANGER: EK cert checking is disabled and no additional checks on EKs have been specified with ek_check_script option. Keylime is not secure!!" ) # check EK cert and make sure it matches EK if not self.check_ek(reg_keys['ek'], reg_keys['ekcert'], tpm): return False # if agent is virtual, check phyisical EK cert and make sure it matches phyiscal EK if 'provider_keys' in reg_keys: if not self.check_ek(reg_keys['provider_keys']['ek'], reg_keys['provider_keys']['ekcert'], tpm): return False # check all EKs with optional script: script = config.get('tenant', 'ek_check_script') if script != "": if script[0] != '/': script = "%s/%s" % (common.WORK_DIR, script) logger.info(f"Checking EK with script {script}") # now we need to exec the script with the ek and ek cert in vars env = os.environ.copy() env['AGENT_UUID'] = self.agent_uuid env['EK'] = reg_keys['ek'] if reg_keys['ekcert'] is not None: env['EK_CERT'] = reg_keys['ekcert'] else: env['EK_CERT'] = "" env['PROVKEYS'] = json.dumps(reg_keys.get('provider_keys', {})) proc = subprocess.Popen(script, env=env, shell=True, cwd=common.WORK_DIR, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) retval = proc.wait() if retval != 0: raise UserError("External check script failed to validate EK") while True: line = proc.stdout.readline() if line == "": break logger.debug(f"ek_check output: {line.strip()}") return False else: logger.debug( "External check script successfully to validated EK") while True: line = proc.stdout.readline() if line == "": break logger.debug(f"ek_check output: {line.strip()}") return True
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 = 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'] try: agent = session.query(RegistrarMain).filter_by( agent_id=agent_id).first() except SQLAlchemyError as e: logger.error(f'SQLAlchemy Error: {e}') 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: try: session.query(RegistrarMain).filter( agent_id == agent_id).update({'active': True}) session.commit() except SQLAlchemyError as e: logger.error(f'SQLAlchemy Error: {e}') else: ex_mac = crypto.do_hmac(agent.key, agent_id) if ex_mac == auth_tag: try: session.query(RegistrarMain).filter( agent_id == agent_id).update({'active': True}) session.commit() except SQLAlchemyError as e: logger.error(f'SQLAlchemy Error: {e}') 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) try: agent = session.query(RegistrarMain).filter_by( agent_id=agent_id).first() except SQLAlchemyError as e: logger.error(f'SQLAlchemy Error: {e}') 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('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( agent_id == agent_id).update({'active': True}) except SQLAlchemyError as e: logger.error(f'SQLAlchemy Error: {e}') try: session.query(RegistrarMain).filter( agent_id == agent_id).update( {'provider_keys': provider_keys}) except SQLAlchemyError as e: logger.error(f'SQLAlchemy Error: {e}') 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_POST(self): """This method handles the POST requests to add agents to the Registrar Server. Currently, only agents resources are available for POSTing, i.e. /agents. All other POST uri's will return errors. POST requests require an an agent_id identifying the agent to add, and json block sent in the body with 2 entries: ek and aik. """ session = SessionManager().make_session(engine) rest_params = 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( 'POST 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( 'POST 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( 'POST 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) ek = json_body['ek'] ek_tpm = json_body['ek_tpm'] ekcert = json_body['ekcert'] aik = json_body['aik'] aik_name = json_body['aik_name'] tpm_version = int(json_body['tpm_version']) # try to encrypt the AIK tpm = tpm_obj.getTPM(need_hw_tpm=False, tpm_version=tpm_version) (blob, key) = tpm.encryptAIK(agent_id, aik, ek, ek_tpm, aik_name) # special behavior if we've registered this uuid before regcount = 1 try: agent = session.query(RegistrarMain).filter_by( agent_id=agent_id).first() except SQLAlchemyError as e: logger.error(f'SQLAlchemy Error: {e}') if agent is not None: # keep track of how many ek-ekcerts have registered on this uuid regcount = agent.regcount if agent.ek != ek or agent.ekcert != ekcert: logger.warning( 'WARNING: Overwriting previous registration for this UUID with new ek-ekcert pair!' ) regcount += 1 # force overwrite logger.info('Overwriting previous registration for this UUID.') try: session.query(RegistrarMain).filter_by( agent_id=agent_id).delete() session.commit() except SQLAlchemyError as e: logger.error(f'SQLAlchemy Error: {e}') # Add values to database d = {} d['agent_id'] = agent_id d['ek'] = ek d['aik'] = aik d['ekcert'] = ekcert d['virtual'] = int(ekcert == 'virtual') d['active'] = int(False) d['key'] = key d['provider_keys'] = {} d['regcount'] = regcount try: session.add(RegistrarMain(**d)) session.commit() except SQLAlchemyError as e: logger.error(f'SQLAlchemy Error: {e}') response = { 'blob': blob, } common.echo_json_response(self, 200, "Success", response) logger.info('POST returning key blob for agent_id: ' + agent_id) return except Exception as e: common.echo_json_response(self, 400, "Error: %s" % e) logger.warning("POST for " + agent_id + " returning 400 response. Error: %s" % e) logger.exception(e) return