def test_010_reg_agent_post(self): """Test registrar's POST /v2/agents/{UUID} Interface""" global keyblob, aik, vtpm, ek # Change CWD for TPM-related operations cwd = os.getcwd() config.ch_dir(config.WORK_DIR, None) _ = secure_mount.mount() # Initialize the TPM with AIK (ek, ekcert, aik, ek_tpm, aik_name) = tpm.tpm_init(self_activate=False, config_pw=config.get('cloud_agent', 'tpm_ownerpassword')) vtpm = tpm.is_vtpm() # Seed RNG (root only) if config.REQUIRE_ROOT: tpm.init_system_rand() # Handle virtualized and emulated TPMs if ekcert is None: if vtpm: ekcert = 'virtual' elif tpm.is_emulator(): ekcert = 'emulator' # Get back to our original CWD config.ch_dir(cwd, None) data = { 'ek': ek, 'ekcert': ekcert, 'aik': aik, 'aik_name': aik_name, 'ek_tpm': ek_tpm, 'tpm_version': tpm.VERSION, } test_010_reg_agent_post = RequestsClient( tenant_templ.registrar_base_url, tls_enabled=False) response = test_010_reg_agent_post.post( f'/v{self.api_version}/agents/{tenant_templ.agent_uuid}', data=json.dumps(data), cert="", verify=False) self.assertEqual(response.status_code, 200, "Non-successful Registrar agent Add return code!") json_response = response.json() # Ensure response is well-formed self.assertIn("results", json_response, "Malformed response body!") self.assertIn("blob", json_response["results"], "Malformed response body!") keyblob = json_response["results"]["blob"] self.assertIsNotNone(keyblob, "Malformed response body!")
def test_010_reg_agent_post(self): """Test registrar's POST /agents/{UUID} Interface""" global keyblob, vtpm, tpm_instance, ek_tpm, aik_tpm contact_ip = "127.0.0.1" contact_port = 9002 tpm_instance = tpm_main.tpm() # Change CWD for TPM-related operations cwd = os.getcwd() config.ch_dir(config.WORK_DIR, None) _ = secure_mount.mount() # Initialize the TPM with AIK (ekcert, ek_tpm, aik_tpm) = tpm_instance.tpm_init( self_activate=False, config_pw=config.get('cloud_agent', 'tpm_ownerpassword')) vtpm = tpm_instance.is_vtpm() # Handle virtualized and emulated TPMs if ekcert is None: if vtpm: ekcert = 'virtual' elif tpm_instance.is_emulator(): ekcert = 'emulator' # Get back to our original CWD config.ch_dir(cwd, None) data = { 'ekcert': ekcert, 'aik_tpm': aik_tpm, 'ip': contact_ip, 'port': contact_port } if ekcert is None or ekcert == 'emulator': data['ek_tpm'] = ek_tpm test_010_reg_agent_post = RequestsClient( tenant_templ.registrar_base_url, tls_enabled=False) response = test_010_reg_agent_post.post( f'/v{self.api_version}/agents/{tenant_templ.agent_uuid}', data=json.dumps(data), cert="", verify=False) self.assertEqual(response.status_code, 200, "Non-successful Registrar agent Add return code!") json_response = response.json() # Ensure response is well-formed self.assertIn("results", json_response, "Malformed response body!") self.assertIn("blob", json_response["results"], "Malformed response body!") keyblob = json_response["results"]["blob"] self.assertIsNotNone(keyblob, "Malformed response body!")
def cmd_mkcert(workingdir, name): cwd = os.getcwd() try: config.ch_dir(workingdir, logger) priv = read_private() cacert = X509.load_cert('cacert.crt') ca_pk = EVP.load_key_string(priv[0]['ca']) cert, pk = ca_impl.mk_signed_cert(cacert, ca_pk, name, priv[0]['lastserial'] + 1) with open('%s-cert.crt' % name, 'wb') as f: f.write(cert.as_pem()) f = BIO.MemoryBuffer() pk.save_key_bio(f, None) priv[0][name] = f.getvalue() f.close() # increment serial number after successful creation priv[0]['lastserial'] += 1 write_private(priv) # write out the private key with password with os.fdopen( os.open("%s-private.pem" % name, os.O_WRONLY | os.O_CREAT, 0o600), 'wb') as f: biofile = BIO.File(f) pk.save_key_bio(biofile, None) biofile.close() pk.get_rsa().save_pub_key('%s-public.pem' % name) cc = X509.load_cert('%s-cert.crt' % name) if cc.verify(cacert.get_pubkey()): logger.info( f"Created certificate for name {name} successfully in {workingdir}" ) else: logger.error("ERROR: Cert does not validate against CA") finally: os.chdir(cwd)
def cmd_revoke(workingdir, name=None, serial=None): cwd = os.getcwd() try: config.ch_dir(workingdir, logger) priv = read_private() if name is not None and serial is not None: raise Exception( "You may not specify a cert and a serial at the same time") if name is None and serial is None: raise Exception("You must specify a cert or a serial to revoke") if name is not None: # load up the cert cert = X509.load_cert("%s-cert.crt" % name) serial = cert.get_serial_number() # convert serial to string serial = str(serial) # get the ca key cert and keys as strings with open('cacert.crt', 'r') as f: cacert = f.read() ca_pk = priv[0]['ca'].decode('utf-8') if serial not in priv[0]['revoked_keys']: priv[0]['revoked_keys'].append(serial) crl = ca_impl.gencrl(priv[0]['revoked_keys'], cacert, ca_pk) write_private(priv) # write out the CRL to the disk if os.stat('cacrl.der').st_size: with open('cacrl.der', 'wb') as f: f.write(crl) convert_crl_to_pem("cacrl.der", "cacrl.pem") finally: os.chdir(cwd) return crl
def cmd_regencrl(workingdir): cwd = os.getcwd() try: config.ch_dir(workingdir, logger) priv = read_private() # get the ca key cert and keys as strings with open('cacert.crt', 'r') as f: cacert = f.read() ca_pk = str(priv[0]['ca']) crl = ca_impl.gencrl(priv[0]['revoked_keys'], cacert, ca_pk) write_private(priv) # write out the CRL to the disk with open('cacrl.der', 'wb') as f: f.write(crl) convert_crl_to_pem("cacrl.der", "cacrl.pem") finally: os.chdir(cwd) return crl
def cmd_listen(workingdir, cert_path): cwd = os.getcwd() try: config.ch_dir(workingdir, logger) # just load up the password for later read_private(True) serveraddr = ('', config.CRL_PORT) server = ThreadedCRLServer(serveraddr, CRLHandler) if os.path.exists('cacrl.der'): logger.info("Loading existing crl: %s" % os.path.abspath("cacrl.der")) with open('cacrl.der', 'rb') as f: server.setcrl(f.read()) t = threading.Thread(target=server.serve_forever) logger.info("Hosting CRL on %s:%d" % (socket.getfqdn(), config.CRL_PORT)) t.start() def check_expiration(): logger.info("checking CRL for expiration every hour") while True: # pylint: disable=R1702 try: if (os.path.exists('cacrl.der') and os.stat('cacrl.der').st_size): cmd = ('openssl', 'crl', '-inform', 'der', '-in', 'cacrl.der', '-text', '-noout') retout = cmd_exec.run(cmd, lock=False)['retout'] for line in retout: line = line.strip() if line.startswith(b"Next Update:"): expire = datetime.datetime.strptime( line[13:].decode('utf-8'), "%b %d %H:%M:%S %Y %Z") # check expiration within 6 hours in1hour = datetime.datetime.utcnow( ) + datetime.timedelta(hours=6) if expire <= in1hour: logger.info( "Certificate to expire soon %s, re-issuing" % expire) cmd_regencrl(workingdir) # check a little less than every hour time.sleep(3540) except KeyboardInterrupt: logger.info("TERM Signal received, shutting down...") # server.shutdown() break t2 = threading.Thread(target=check_expiration) t2.setDaemon(True) t2.start() def revoke_callback(revocation): json_meta = json.loads(revocation['meta_data']) serial = json_meta['cert_serial'] if revocation.get('type', None) != 'revocation' or serial is None: logger.error("Unsupported revocation message: %s" % revocation) return logger.info("Revoking certificate: %s" % serial) server.setcrl(cmd_revoke(workingdir, None, serial)) try: while True: try: revocation_notifier.await_notifications( revoke_callback, revocation_cert_path=cert_path) except Exception as e: logger.exception(e) logger.warning( "No connection to revocation server, retrying in 10s..." ) time.sleep(10) except KeyboardInterrupt: logger.info("TERM Signal received, shutting down...") server.shutdown() sys.exit() finally: os.chdir(cwd)
def cmd_certpkg(workingdir, name, insecure=False): cwd = os.getcwd() try: config.ch_dir(workingdir, logger) # zip up the crt, private key, and public key with open('cacert.crt', 'r') as f: cacert = f.read() with open("%s-public.pem" % name, 'r') as f: pub = f.read() with open("%s-cert.crt" % name, 'r') as f: cert = f.read() with open('cacrl.der', 'rb') as f: crl = f.read() with open('cacrl.pem', 'r') as f: crlpem = f.read() cert_obj = X509.load_cert_string(cert) serial = cert_obj.get_serial_number() subject = str(cert_obj.get_subject()) priv = read_private() private = priv[0][name] with open("%s-private.pem" % name, 'r') as f: prot_priv = f.read() # code to create a pem formatted protected private key using the keystore password # pk = EVP.load_key_string(str(priv[0][name])) # f = BIO.MemoryBuffer() # # globalcb will return the global password provided by the user # pk.save_key_bio(f, 'aes_256_cbc', globalcb) # prot_priv = f.getvalue() # f.close() # no compression to avoid extraction errors in tmpfs sf = io.BytesIO() with zipfile.ZipFile(sf, 'w', compression=zipfile.ZIP_STORED) as f: f.writestr('%s-public.pem' % name, pub) f.writestr('%s-cert.crt' % name, cert) f.writestr('%s-private.pem' % name, private) f.writestr('cacert.crt', cacert) f.writestr('cacrl.der', crl) f.writestr('cacrl.pem', crlpem) pkg = sf.getvalue() if insecure: logger.warn( "Unprotected private keys in cert package being written to disk" ) with open('%s-pkg.zip' % name, 'w') as f: f.write(pkg) else: # actually output the package to disk with a protected private key with zipfile.ZipFile('%s-pkg.zip' % name, 'w', compression=zipfile.ZIP_STORED) as f: f.writestr('%s-public.pem' % name, pub) f.writestr('%s-cert.crt' % name, cert) f.writestr('%s-private.pem' % name, prot_priv) f.writestr('cacert.crt', cacert) f.writestr('cacrl.der', crl) f.writestr('cacrl.pem', crlpem) logger.info("Creating cert package for %s in %s-pkg.zip" % (name, name)) return pkg, serial, subject finally: os.chdir(cwd)
def cmd_init(workingdir): cwd = os.getcwd() try: config.ch_dir(workingdir, logger) rmfiles("*.pem") rmfiles("*.crt") rmfiles("*.zip") rmfiles("*.der") rmfiles("private.yml") if config.CA_IMPL == 'cfssl': pk_str, cacert, ca_pk, _ = ca_impl.mk_cacert() elif config.CA_IMPL == 'openssl': cacert, ca_pk, _ = ca_impl.mk_cacert() # pylint: disable=W0632 else: raise Exception("Unknown CA implementation: %s" % config.CA_IMPL) priv = read_private() # write out keys with open('cacert.crt', 'wb') as f: f.write(cacert.as_pem()) f = BIO.MemoryBuffer() ca_pk.save_key_bio(f, None) priv[0]['ca'] = f.getvalue() f.close() # store the last serial number created. # the CA is always serial # 1 priv[0]['lastserial'] = 1 write_private(priv) ca_pk.get_rsa().save_pub_key('ca-public.pem') # generate an empty crl if config.CA_IMPL == 'cfssl': crl = ca_impl.gencrl([], cacert.as_pem(), pk_str) elif config.CA_IMPL == 'openssl': crl = ca_impl.gencrl([], cacert.as_pem(), str(priv[0]['ca'])) else: raise Exception("Unknown CA implementation: %s" % config.CA_IMPL) if isinstance(crl, str): crl = crl.encode('utf-8') with open('cacrl.der', 'wb') as f: f.write(crl) convert_crl_to_pem("cacrl.der", "cacrl.pem") # Sanity checks... cac = X509.load_cert('cacert.crt') if cac.verify(): logger.info("CA certificate created successfully in %s" % workingdir) else: logger.error("ERROR: Cert does not self validate") finally: os.chdir(cwd)
def main(): for ML in [config.MEASUREDBOOT_ML, config.IMA_ML]: if not os.access(ML, os.F_OK): logger.warning( "Measurement list path %s not accessible by agent. Any attempt to instruct it to access this path - via \"keylime_tenant\" CLI - will result in agent process dying", ML) if config.get('cloud_agent', 'agent_uuid') == 'dmidecode': if os.getuid() != 0: raise RuntimeError('agent_uuid is configured to use dmidecode, ' 'but current process is not running as root.') cmd = ['which', 'dmidecode'] ret = cmd_exec.run(cmd, raiseOnError=False) if ret['code'] != 0: raise RuntimeError('agent_uuid is configured to use dmidecode, ' 'but it\'s is not found on the system.') # Instanitate TPM class instance_tpm = tpm() # get params for initialization registrar_ip = config.get('cloud_agent', 'registrar_ip') registrar_port = config.get('cloud_agent', 'registrar_port') # initialize the tmpfs partition to store keys if it isn't already available secdir = secure_mount.mount() # change dir to working dir config.ch_dir(config.WORK_DIR, logger) # initialize tpm (ekcert, ek_tpm, aik_tpm) = instance_tpm.tpm_init( self_activate=False, config_pw=config.get('cloud_agent', 'tpm_ownerpassword') ) # this tells initialize not to self activate the AIK virtual_agent = instance_tpm.is_vtpm() # try to get some TPM randomness into the system entropy pool instance_tpm.init_system_rand() if ekcert is None: if virtual_agent: ekcert = 'virtual' elif instance_tpm.is_emulator(): ekcert = 'emulator' # now we need the UUID try: agent_uuid = config.get('cloud_agent', 'agent_uuid') except configparser.NoOptionError: agent_uuid = None if agent_uuid == 'openstack': agent_uuid = openstack.get_openstack_uuid() elif agent_uuid == 'hash_ek': agent_uuid = hashlib.sha256(ek_tpm).hexdigest() elif agent_uuid == 'generate' or agent_uuid is None: agent_uuid = str(uuid.uuid4()) elif agent_uuid == 'dmidecode': cmd = ['dmidecode', '-s', 'system-uuid'] ret = cmd_exec.run(cmd) sys_uuid = ret['retout'].decode('utf-8') agent_uuid = sys_uuid.strip() elif agent_uuid == 'hostname': agent_uuid = socket.getfqdn() if config.STUB_VTPM and config.TPM_CANNED_VALUES is not None: # Use canned values for stubbing jsonIn = config.TPM_CANNED_VALUES if "add_vtpm_to_group" in jsonIn: # The value we're looking for has been canned! agent_uuid = jsonIn['add_vtpm_to_group']['retout'] else: # Our command hasn't been canned! raise Exception("Command %s not found in canned json!" % ("add_vtpm_to_group")) logger.info("Agent UUID: %s", agent_uuid) # register it and get back a blob keyblob = registrar_client.doRegisterAgent(registrar_ip, registrar_port, agent_uuid, ek_tpm, ekcert, aik_tpm) if keyblob is None: instance_tpm.flush_keys() raise Exception("Registration failed") # get the ephemeral registrar key key = instance_tpm.activate_identity(keyblob) if key is None: instance_tpm.flush_keys() raise Exception("Activation failed") # tell the registrar server we know the key retval = False retval = registrar_client.doActivateAgent(registrar_ip, registrar_port, agent_uuid, key) if not retval: instance_tpm.flush_keys() raise Exception("Registration failed on activate") serveraddr = (config.get('cloud_agent', 'cloudagent_ip'), config.getint('cloud_agent', 'cloudagent_port')) server = CloudAgentHTTPServer(serveraddr, Handler, agent_uuid) serverthread = threading.Thread(target=server.serve_forever) logger.info("Starting Cloud Agent on %s:%s use <Ctrl-C> to stop", serveraddr[0], serveraddr[1]) serverthread.start() # want to listen for revocations? if config.getboolean('cloud_agent', 'listen_notfications'): cert_path = config.get('cloud_agent', 'revocation_cert') if cert_path == "default": cert_path = '%s/unzipped/RevocationNotifier-cert.crt' % (secdir) elif cert_path[0] != '/': # if it is a relative, convert to absolute in work_dir cert_path = os.path.abspath('%s/%s' % (config.WORK_DIR, cert_path)) def perform_actions(revocation): actionlist = [] # load the actions from inside the keylime module actionlisttxt = config.get('cloud_agent', 'revocation_actions') if actionlisttxt.strip() != "": actionlist = actionlisttxt.split(',') actionlist = ["revocation_actions.%s" % i for i in actionlist] # load actions from unzipped if os.path.exists("%s/unzipped/action_list" % secdir): with open("%s/unzipped/action_list" % secdir, 'r') as f: actionlisttxt = f.read() if actionlisttxt.strip() != "": localactions = actionlisttxt.strip().split(',') for action in localactions: if not action.startswith('local_action_'): logger.warning( "Invalid local action: %s. Must start with local_action_", action) else: actionlist.append(action) uzpath = "%s/unzipped" % secdir if uzpath not in sys.path: sys.path.append(uzpath) for action in actionlist: logger.info("Executing revocation action %s", action) try: module = importlib.import_module(action) execute = getattr(module, 'execute') asyncio.get_event_loop().run_until_complete( execute(revocation)) except Exception as e: logger.warning( "Exception during execution of revocation action %s: %s", action, e) try: while True: try: revocation_notifier.await_notifications( perform_actions, revocation_cert_path=cert_path) except Exception as e: logger.exception(e) logger.warning( "No connection to revocation server, retrying in 10s..." ) time.sleep(10) except KeyboardInterrupt: logger.info("TERM Signal received, shutting down...") instance_tpm.flush_keys() server.shutdown() else: try: while True: time.sleep(1) except KeyboardInterrupt: logger.info("TERM Signal received, shutting down...") instance_tpm.flush_keys() server.shutdown()
def cmd_certpkg(workingdir, name, insecure=False): cwd = os.getcwd() try: config.ch_dir(workingdir, logger) # zip up the crt, private key, and public key with open('cacert.crt', 'r') as f: cacert = f.read() with open("%s-public.pem" % name, 'r') as f: pub = f.read() with open("%s-cert.crt" % name, 'r') as f: cert = f.read() with open('cacrl.der', 'rb') as f: crl = f.read() with open('cacrl.pem', 'r') as f: crlpem = f.read() cert_obj = X509.load_cert_string(cert) serial = cert_obj.get_serial_number() subject = str(cert_obj.get_subject()) priv = read_private() private = priv[0][name] with open("%s-private.pem" % name, 'r') as f: prot_priv = f.read() # no compression to avoid extraction errors in tmpfs sf = io.BytesIO() with zipfile.ZipFile(sf, 'w', compression=zipfile.ZIP_STORED) as f: f.writestr('%s-public.pem' % name, pub) f.writestr('%s-cert.crt' % name, cert) f.writestr('%s-private.pem' % name, private) f.writestr('cacert.crt', cacert) f.writestr('cacrl.der', crl) f.writestr('cacrl.pem', crlpem) pkg = sf.getvalue() if insecure: logger.warning( "Unprotected private keys in cert package being written to disk") with open('%s-pkg.zip' % name, 'w') as f: f.write(pkg) else: # actually output the package to disk with a protected private key with zipfile.ZipFile('%s-pkg.zip' % name, 'w', compression=zipfile.ZIP_STORED) as f: f.writestr('%s-public.pem' % name, pub) f.writestr('%s-cert.crt' % name, cert) f.writestr('%s-private.pem' % name, prot_priv) f.writestr('cacert.crt', cacert) f.writestr('cacrl.der', crl) f.writestr('cacrl.pem', crlpem) logger.info("Creating cert package for %s in %s-pkg.zip" % (name, name)) return pkg, serial, subject finally: os.chdir(cwd)