def init_add(self, args): # command line options can overwrite config values if "agent_ip" in args: self.cloudagent_ip = args["agent_ip"] if 'agent_port' in args and args['agent_port'] is not None: self.cloudagent_port = args['agent_port'] if 'cv_agent_ip' in args and args['cv_agent_ip'] is not None: self.cv_cloudagent_ip = args['cv_agent_ip'] else: self.cv_cloudagent_ip = self.cloudagent_ip # Make sure all keys exist in dictionary if "file" not in args: args["file"] = None if "keyfile" not in args: args["keyfile"] = None if "payload" not in args: args["payload"] = None if "ca_dir" not in args: args["ca_dir"] = None if "incl_dir" not in args: args["incl_dir"] = None if "ca_dir_pw" not in args: args["ca_dir_pw"] = None # Set up accepted algorithms self.accept_tpm_hash_algs = config.get( 'tenant', 'accept_tpm_hash_algs').split(',') self.accept_tpm_encryption_algs = config.get( 'tenant', 'accept_tpm_encryption_algs').split(',') self.accept_tpm_signing_algs = config.get( 'tenant', 'accept_tpm_signing_algs').split(',') # Set up PCR values tpm_policy = config.get('tenant', 'tpm_policy') if "tpm_policy" in args and args["tpm_policy"] is not None: tpm_policy = args["tpm_policy"] self.tpm_policy = TPM_Utilities.readPolicy(tpm_policy) logger.info(f"TPM PCR Mask from policy is {self.tpm_policy['mask']}") vtpm_policy = config.get('tenant', 'vtpm_policy') if "vtpm_policy" in args and args["vtpm_policy"] is not None: vtpm_policy = args["vtpm_policy"] self.vtpm_policy = TPM_Utilities.readPolicy(vtpm_policy) logger.info(f"TPM PCR Mask from policy is {self.vtpm_policy['mask']}") # Read command-line path string IMA whitelist wl_data = None if "ima_whitelist" in args and args["ima_whitelist"] is not None: # Auto-enable IMA (or-bit mask) self.tpm_policy['mask'] = "0x%X" % ( int(self.tpm_policy['mask'], 0) + (1 << common.IMA_PCR)) if type(args["ima_whitelist"]) in [str, str]: if args["ima_whitelist"] == "default": args["ima_whitelist"] = config.get('tenant', 'ima_whitelist') wl_data = ima.read_whitelist(args["ima_whitelist"]) elif type(args["ima_whitelist"]) is list: wl_data = args["ima_whitelist"] else: raise UserError("Invalid whitelist provided") # Read command-line path string IMA exclude list excl_data = None if "ima_exclude" in args and args["ima_exclude"] is not None: if type(args["ima_exclude"]) in [str, str]: if args["ima_exclude"] == "default": args["ima_exclude"] = config.get('tenant', 'ima_excludelist') excl_data = ima.read_excllist(args["ima_exclude"]) elif type(args["ima_exclude"]) is list: excl_data = args["ima_exclude"] else: raise UserError("Invalid exclude list provided") # Set up IMA if TPM_Utilities.check_mask(self.tpm_policy['mask'], common.IMA_PCR) or \ TPM_Utilities.check_mask(self.vtpm_policy['mask'], common.IMA_PCR): # Process IMA whitelists self.ima_whitelist = ima.process_whitelists(wl_data, excl_data) # if none if (args["file"] is None and args["keyfile"] is None and args["ca_dir"] is None): raise UserError( "You must specify one of -k, -f, or --cert to specify the key/contents to be securely delivered to the agent" ) if args["keyfile"] is not None: if args["file"] is not None or args["ca_dir"] is not None: raise UserError( "You must specify one of -k, -f, or --cert to specify the key/contents to be securely delivered to the agent" ) # read the keys in if type(args["keyfile"]) is dict and "data" in args["keyfile"]: if type(args["keyfile"]["data"]) is list and len( args["keyfile"]["data"]) == 1: keyfile = args["keyfile"]["data"][0] if keyfile is None: raise UserError("Invalid key file contents") f = io.StringIO(keyfile) else: raise UserError("Invalid key file provided") else: f = open(args["keyfile"], 'r') self.K = base64.b64decode(f.readline()) self.U = base64.b64decode(f.readline()) self.V = base64.b64decode(f.readline()) f.close() # read the payload in (opt.) if type(args["payload"]) is dict and "data" in args["payload"]: if type(args["payload"]["data"]) is list and len( args["payload"]["data"]) > 0: self.payload = args["payload"]["data"][0] else: if args["payload"] is not None: f = open(args["payload"], 'r') self.payload = f.read() f.close() if args["file"] is not None: if args["keyfile"] is not None or args["ca_dir"] is not None: raise UserError( "You must specify one of -k, -f, or --cert to specify the key/contents to be securely delivered to the agent" ) if type(args["file"]) is dict and "data" in args["file"]: if type(args["file"]["data"]) is list and len( args["file"]["data"]) > 0: contents = args["file"]["data"][0] if contents is None: raise UserError("Invalid file payload contents") else: raise UserError("Invalid file payload provided") else: with open(args["file"], 'r') as f: contents = f.read() ret = user_data_encrypt.encrypt(contents) self.K = ret['k'] self.U = ret['u'] self.V = ret['v'] self.payload = ret['ciphertext'] if args["ca_dir"] is None and args["incl_dir"] is not None: raise UserError( "--include option is only valid when used with --cert") if args["ca_dir"] is not None: if args["file"] is not None or args["keyfile"] is not None: raise UserError( "You must specify one of -k, -f, or --cert to specify the key/contents to be securely delivered to the agent" ) if args["ca_dir"] == 'default': args["ca_dir"] = common.CA_WORK_DIR if "ca_dir_pw" in args and args["ca_dir_pw"] is not None: ca_util.setpassword(args["ca_dir_pw"]) if not os.path.exists(args["ca_dir"]) or not os.path.exists( "%s/cacert.crt" % args["ca_dir"]): logger.warning(" CA directory does not exist. Creating...") ca_util.cmd_init(args["ca_dir"]) if not os.path.exists("%s/%s-private.pem" % (args["ca_dir"], self.agent_uuid)): ca_util.cmd_mkcert(args["ca_dir"], self.agent_uuid) cert_pkg, serial, subject = ca_util.cmd_certpkg( args["ca_dir"], self.agent_uuid) # support revocation if not os.path.exists( "%s/RevocationNotifier-private.pem" % args["ca_dir"]): ca_util.cmd_mkcert(args["ca_dir"], "RevocationNotifier") rev_package, _, _ = ca_util.cmd_certpkg(args["ca_dir"], "RevocationNotifier") # extract public and private keys from package sf = io.BytesIO(rev_package) with zipfile.ZipFile(sf) as zf: privkey = zf.read("RevocationNotifier-private.pem") cert = zf.read("RevocationNotifier-cert.crt") # put the cert of the revoker into the cert package sf = io.BytesIO(cert_pkg) with zipfile.ZipFile(sf, 'a', compression=zipfile.ZIP_STORED) as zf: zf.writestr('RevocationNotifier-cert.crt', cert) # add additional files to zip if args["incl_dir"] is not None: if type(args["incl_dir"]) is dict and "data" in args[ "incl_dir"] and "name" in args["incl_dir"]: if type(args["incl_dir"]["data"]) is list and type( args["incl_dir"]["name"]) is list: if len(args["incl_dir"]["data"]) != len( args["incl_dir"]["name"]): raise UserError("Invalid incl_dir provided") for i in range(len(args["incl_dir"]["data"])): zf.writestr( os.path.basename( args["incl_dir"]["name"][i]), args["incl_dir"]["data"][i]) else: if os.path.exists(args["incl_dir"]): files = next(os.walk(args["incl_dir"]))[2] for filename in files: with open( "%s/%s" % (args["incl_dir"], filename), 'rb') as f: zf.writestr(os.path.basename(f.name), f.read()) else: logger.warn( f'Specified include directory {args["incl_dir"]} does not exist. Skipping...' ) cert_pkg = sf.getvalue() # put the private key into the data to be send to the CV self.revocation_key = privkey # encrypt up the cert package ret = user_data_encrypt.encrypt(cert_pkg) self.K = ret['k'] self.U = ret['u'] self.V = ret['v'] self.metadata = {'cert_serial': serial, 'subject': subject} self.payload = ret['ciphertext'] if self.payload is not None and len(self.payload) > config.getint( 'tenant', 'max_payload_size'): raise UserError("Payload size %s exceeds max size %d" % (len( self.payload), config.getint('tenant', 'max_payload_size')))
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 process_allowlist(self, args): # Set up PCR values tpm_policy = config.get('tenant', 'tpm_policy') if "tpm_policy" in args and args["tpm_policy"] is not None: tpm_policy = args["tpm_policy"] self.tpm_policy = TPM_Utilities.readPolicy(tpm_policy) logger.info("TPM PCR Mask from policy is %s", self.tpm_policy['mask']) vtpm_policy = config.get('tenant', 'vtpm_policy') if "vtpm_policy" in args and args["vtpm_policy"] is not None: vtpm_policy = args["vtpm_policy"] self.vtpm_policy = TPM_Utilities.readPolicy(vtpm_policy) logger.info("TPM PCR Mask from policy is %s", self.vtpm_policy['mask']) if args.get("ima_sign_verification_keys") is not None: # Auto-enable IMA (or-bit mask) self.tpm_policy['mask'] = "0x%X" % ( int(self.tpm_policy['mask'], 0) | (1 << config.IMA_PCR)) # Add all IMA file signing verification keys to a keyring ima_keyring = ima_file_signatures.ImaKeyring() for filename in args["ima_sign_verification_keys"]: pubkey = ima_file_signatures.get_pubkey_from_file(filename) if not pubkey: raise UserError( "File '%s' is not a file with a key" % filename) ima_keyring.add_pubkey(pubkey) self.ima_sign_verification_keys = ima_keyring.to_string() # Read command-line path string allowlist al_data = None if "allowlist" in args and args["allowlist"] is not None: self.enforce_pcrs(list(self.tpm_policy.keys()), [ config.IMA_PCR ], "IMA") # Auto-enable IMA (or-bit mask) self.tpm_policy['mask'] = "0x%X" % ( int(self.tpm_policy['mask'], 0) | (1 << config.IMA_PCR)) if isinstance(args["allowlist"], str): if args["allowlist"] == "default": args["allowlist"] = config.get('tenant', 'allowlist') al_data = ima.read_allowlist(args["allowlist"], args["allowlist_checksum"], args["allowlist_sig"], args["allowlist_sig_key"]) elif isinstance(args["allowlist"], list): al_data = args["allowlist"] else: raise UserError("Invalid allowlist provided") # Read command-line path string IMA exclude list excl_data = None if "ima_exclude" in args and args["ima_exclude"] is not None: if isinstance(args["ima_exclude"], str): if args["ima_exclude"] == "default": args["ima_exclude"] = config.get( 'tenant', 'ima_excludelist') excl_data = ima.read_excllist(args["ima_exclude"]) elif isinstance(args["ima_exclude"], list): excl_data = args["ima_exclude"] else: raise UserError("Invalid exclude list provided") # Set up IMA if TPM_Utilities.check_mask(self.tpm_policy['mask'], config.IMA_PCR) or \ TPM_Utilities.check_mask(self.vtpm_policy['mask'], config.IMA_PCR): # Process allowlists self.allowlist = ima.process_allowlists(al_data, excl_data) # Read command-line path string TPM event log (measured boot) reference state mb_refstate_data = None if "mb_refstate" in args and args["mb_refstate"] is not None: self.enforce_pcrs(list(self.tpm_policy.keys()), config.MEASUREDBOOT_PCRS, "measured boot") # Auto-enable TPM event log mesured boot (or-bit mask) for _pcr in config.MEASUREDBOOT_PCRS : self.tpm_policy['mask'] = "0x%X" % ( int(self.tpm_policy['mask'], 0) | (1 << _pcr)) logger.info("TPM PCR Mask automatically modified is %s to include IMA/Event log PCRs", self.tpm_policy['mask']) if isinstance(args["mb_refstate"], str): if args["mb_refstate"] == "default": args["mb_refstate"] = config.get('tenant', 'mb_refstate') mb_refstate_data = measured_boot.read_mb_refstate(args["mb_refstate"]) else: raise UserError("Invalid measured boot reference state (intended state) provided") # Set up measured boot (TPM event log) reference state if TPM_Utilities.check_mask(self.tpm_policy['mask'], config.MEASUREDBOOT_PCRS[2]) : # Process measured boot reference state self.mb_refstate = measured_boot.process_refstate(mb_refstate_data)
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_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 init_add(self, args): """ Set up required values. Command line options can overwrite these config values Arguments: args {[string]} -- agent_ip|agent_port|cv_agent_ip """ if "agent_ip" in args: self.agent_ip = args["agent_ip"] if 'agent_port' in args and args['agent_port'] is not None: self.agent_port = args['agent_port'] if 'cv_agent_ip' in args and args['cv_agent_ip'] is not None: self.cv_cloudagent_ip = args['cv_agent_ip'] else: self.cv_cloudagent_ip = self.agent_ip # Make sure all keys exist in dictionary if "file" not in args: args["file"] = None if "keyfile" not in args: args["keyfile"] = None if "payload" not in args: args["payload"] = None if "ca_dir" not in args: args["ca_dir"] = None if "incl_dir" not in args: args["incl_dir"] = None if "ca_dir_pw" not in args: args["ca_dir_pw"] = None # Set up accepted algorithms self.accept_tpm_hash_algs = config.get( 'tenant', 'accept_tpm_hash_algs').split(',') self.accept_tpm_encryption_algs = config.get( 'tenant', 'accept_tpm_encryption_algs').split(',') self.accept_tpm_signing_algs = config.get( 'tenant', 'accept_tpm_signing_algs').split(',') # Set up PCR values tpm_policy = config.get('tenant', 'tpm_policy') if "tpm_policy" in args and args["tpm_policy"] is not None: tpm_policy = args["tpm_policy"] self.tpm_policy = TPM_Utilities.readPolicy(tpm_policy) logger.info(f"TPM PCR Mask from policy is {self.tpm_policy['mask']}") vtpm_policy = config.get('tenant', 'vtpm_policy') if "vtpm_policy" in args and args["vtpm_policy"] is not None: vtpm_policy = args["vtpm_policy"] self.vtpm_policy = TPM_Utilities.readPolicy(vtpm_policy) logger.info(f"TPM PCR Mask from policy is {self.vtpm_policy['mask']}") if args.get("ima_sign_verification_keys") is not None: # Auto-enable IMA (or-bit mask) self.tpm_policy['mask'] = "0x%X" % (int(self.tpm_policy['mask'], 0) | (1 << config.IMA_PCR)) # Add all IMA file signing verification keys to a keyring ima_keyring = ima_file_signatures.ImaKeyring() for filename in args["ima_sign_verification_keys"]: pubkey = ima_file_signatures.get_pubkey_from_file(filename) if not pubkey: raise UserError("File '%s' is not a file with a key" % filename) ima_keyring.add_pubkey(pubkey) self.ima_sign_verification_keys = ima_keyring.to_string() # Read command-line path string allowlist al_data = None if "allowlist" in args and args["allowlist"] is not None: self.enforce_pcrs(list(self.tpm_policy.keys()), [config.IMA_PCR], "IMA") # Auto-enable IMA (or-bit mask) self.tpm_policy['mask'] = "0x%X" % (int(self.tpm_policy['mask'], 0) | (1 << config.IMA_PCR)) if isinstance(args["allowlist"], str): if args["allowlist"] == "default": args["allowlist"] = config.get('tenant', 'allowlist') al_data = ima.read_allowlist(args["allowlist"]) elif isinstance(args["allowlist"], list): al_data = args["allowlist"] else: raise UserError("Invalid allowlist provided") # Read command-line path string IMA exclude list excl_data = None if "ima_exclude" in args and args["ima_exclude"] is not None: if isinstance(args["ima_exclude"], str): if args["ima_exclude"] == "default": args["ima_exclude"] = config.get('tenant', 'ima_excludelist') excl_data = ima.read_excllist(args["ima_exclude"]) elif isinstance(args["ima_exclude"], list): excl_data = args["ima_exclude"] else: raise UserError("Invalid exclude list provided") # Set up IMA if TPM_Utilities.check_mask(self.tpm_policy['mask'], config.IMA_PCR) or \ TPM_Utilities.check_mask(self.vtpm_policy['mask'], config.IMA_PCR): # Process allowlists self.allowlist = ima.process_allowlists(al_data, excl_data) # Read command-line path string TPM2 event log template if "mb_refstate" in args and args["mb_refstate"] is not None: self.enforce_pcrs(list(self.tpm_policy.keys()), config.MEASUREDBOOT_PCRS, "measured boot") # Auto-enable TPM2 event log (or-bit mask) for _pcr in config.MEASUREDBOOT_PCRS: self.tpm_policy['mask'] = "0x%X" % (int( self.tpm_policy['mask'], 0) | (1 << _pcr)) logger.info( f"TPM PCR Mask automatically modified is {self.tpm_policy['mask']} to include IMA/Event log PCRs" ) if isinstance(args["mb_refstate"], str): if args["mb_refstate"] == "default": args["mb_refstate"] = config.get('tenant', 'mb_refstate') al_data = 'test' elif isinstance(args["mb_refstate"], list): al_data = args["mb_refstate"] else: raise UserError( "Invalid measured boot reference state (intended state) provided" ) # if none if (args["file"] is None and args["keyfile"] is None and args["ca_dir"] is None): raise UserError( "You must specify one of -k, -f, or --cert to specify the key/contents to be securely delivered to the agent" ) if args["keyfile"] is not None: if args["file"] is not None or args["ca_dir"] is not None: raise UserError( "You must specify one of -k, -f, or --cert to specify the key/contents to be securely delivered to the agent" ) # read the keys in if isinstance(args["keyfile"], dict) and "data" in args["keyfile"]: if isinstance(args["keyfile"]["data"], list) and len( args["keyfile"]["data"]) == 1: keyfile = args["keyfile"]["data"][0] if keyfile is None: raise UserError("Invalid key file contents") f = io.StringIO(keyfile) else: raise UserError("Invalid key file provided") else: f = open(args["keyfile"], 'r') self.K = base64.b64decode(f.readline()) self.U = base64.b64decode(f.readline()) self.V = base64.b64decode(f.readline()) f.close() # read the payload in (opt.) if isinstance(args["payload"], dict) and "data" in args["payload"]: if isinstance(args["payload"]["data"], list) and len(args["payload"]["data"]) > 0: self.payload = args["payload"]["data"][0] else: if args["payload"] is not None: f = open(args["payload"], 'r') self.payload = f.read() f.close() if args["file"] is not None: if args["keyfile"] is not None or args["ca_dir"] is not None: raise UserError( "You must specify one of -k, -f, or --cert to specify the key/contents to be securely delivered to the agent" ) if isinstance(args["file"], dict) and "data" in args["file"]: if isinstance(args["file"]["data"], list) and len(args["file"]["data"]) > 0: contents = args["file"]["data"][0] if contents is None: raise UserError("Invalid file payload contents") else: raise UserError("Invalid file payload provided") else: with open(args["file"], 'r') as f: contents = f.read() ret = user_data_encrypt.encrypt(contents) self.K = ret['k'] self.U = ret['u'] self.V = ret['v'] self.payload = ret['ciphertext'] if args["ca_dir"] is None and args["incl_dir"] is not None: raise UserError( "--include option is only valid when used with --cert") if args["ca_dir"] is not None: if args["file"] is not None or args["keyfile"] is not None: raise UserError( "You must specify one of -k, -f, or --cert to specify the key/contents to be securely delivered to the agent" ) if args["ca_dir"] == 'default': args["ca_dir"] = config.CA_WORK_DIR if "ca_dir_pw" in args and args["ca_dir_pw"] is not None: ca_util.setpassword(args["ca_dir_pw"]) if not os.path.exists(args["ca_dir"]) or not os.path.exists( "%s/cacert.crt" % args["ca_dir"]): logger.warning(" CA directory does not exist. Creating...") ca_util.cmd_init(args["ca_dir"]) if not os.path.exists("%s/%s-private.pem" % (args["ca_dir"], self.agent_uuid)): ca_util.cmd_mkcert(args["ca_dir"], self.agent_uuid) cert_pkg, serial, subject = ca_util.cmd_certpkg( args["ca_dir"], self.agent_uuid) # support revocation if not os.path.exists( "%s/RevocationNotifier-private.pem" % args["ca_dir"]): ca_util.cmd_mkcert(args["ca_dir"], "RevocationNotifier") rev_package, _, _ = ca_util.cmd_certpkg(args["ca_dir"], "RevocationNotifier") # extract public and private keys from package sf = io.BytesIO(rev_package) with zipfile.ZipFile(sf) as zf: privkey = zf.read("RevocationNotifier-private.pem") cert = zf.read("RevocationNotifier-cert.crt") # put the cert of the revoker into the cert package sf = io.BytesIO(cert_pkg) with zipfile.ZipFile(sf, 'a', compression=zipfile.ZIP_STORED) as zf: zf.writestr('RevocationNotifier-cert.crt', cert) # add additional files to zip if args["incl_dir"] is not None: if isinstance(args["incl_dir"], dict) and "data" in args[ "incl_dir"] and "name" in args["incl_dir"]: if isinstance(args["incl_dir"]["data"], list) and isinstance( args["incl_dir"]["name"], list): if len(args["incl_dir"]["data"]) != len( args["incl_dir"]["name"]): raise UserError("Invalid incl_dir provided") for i in range(len(args["incl_dir"]["data"])): zf.writestr( os.path.basename( args["incl_dir"]["name"][i]), args["incl_dir"]["data"][i]) else: if os.path.exists(args["incl_dir"]): files = next(os.walk(args["incl_dir"]))[2] for filename in files: with open( "%s/%s" % (args["incl_dir"], filename), 'rb') as f: zf.writestr(os.path.basename(f.name), f.read()) else: logger.warning( f'Specified include directory {args["incl_dir"]} does not exist. Skipping...' ) cert_pkg = sf.getvalue() # put the private key into the data to be send to the CV self.revocation_key = privkey # encrypt up the cert package ret = user_data_encrypt.encrypt(cert_pkg) self.K = ret['k'] self.U = ret['u'] self.V = ret['v'] self.metadata = {'cert_serial': serial, 'subject': subject} self.payload = ret['ciphertext'] if self.payload is not None and len(self.payload) > config.getint( 'tenant', 'max_payload_size'): raise UserError("Payload size %s exceeds max size %d" % (len( self.payload), config.getint('tenant', 'max_payload_size')))