def validate_agent_data(agent_data): if agent_data is None: return False, None # validate that the allowlist is proper JSON lists = json.loads(agent_data['allowlist']) # Validate exlude list contains valid regular expressions is_valid, _, err_msg = config.valid_exclude_list(lists.get('exclude')) if not is_valid: err_msg += " Exclude list regex is misformatted. Please correct the issue and try again." return is_valid, err_msg
def process_measurement_list(lines, lists=None, m2w=None, pcrval=None): errs = [0, 0, 0, 0] runninghash = START_HASH found_pcr = (pcrval is None) if lists is not None: lists = ast.literal_eval(lists) allowlist = lists['allowlist'] exclude_list = lists['exclude'] else: allowlist = None exclude_list = None is_valid, compiled_regex, err_msg = config.valid_exclude_list(exclude_list) if not is_valid: # This should not happen as the exclude list has already been validated # by the verifier before acceping it. This is a safety net just in case. err_msg += " Exclude list will be ignored." logger.error(err_msg) for line in lines: line = line.strip() if line == '': continue tokens = line.split(None, 4) if len(tokens) != 5: logger.error("invalid measurement list file line: -%s-" % (line)) return None # print tokens # pcr = tokens[0] template_hash = codecs.decode(tokens[1], 'hex') mode = tokens[2] if mode == "ima-ng": filedata_hash, path, error = _extract_from_ima_ng( tokens, template_hash) elif mode == 'ima': filedata_hash, path, error = _extract_from_ima( tokens, template_hash) else: raise Exception("unsupported ima template mode: %s" % mode) errs[0] += error if template_hash == START_HASH: template_hash = FF_HASH # update hash runninghash = hashlib.sha1(runninghash + template_hash).digest() if not found_pcr: found_pcr = \ (codecs.encode(runninghash, 'hex').decode('utf-8') == pcrval) # write out the new hash if m2w is not None: m2w.write( "%s %s\n" % (codecs.encode(filedata_hash, 'hex').decode('utf-8'), path)) if allowlist is not None: # just skip if it is a weird overwritten path if template_hash == FF_HASH: # print "excluding ffhash %s"%path continue # determine if path matches any exclusion list items if compiled_regex is not None and compiled_regex.match(path): logger.debug("IMA: ignoring excluded path %s" % path) continue accept_list = allowlist.get(path, None) if accept_list is None: logger.warning("File not found in allowlist: %s" % (path)) errs[1] += 1 continue # print('codecs.encode', codecs.encode(filedata_hash, 'hex').decode('utf-8')) # print('accept_list:', accept_list) if codecs.encode(filedata_hash, 'hex').decode('utf-8') not in accept_list: logger.warning( "Hashes for file %s don't match %s not in %s" % (path, codecs.encode(filedata_hash, 'hex').decode('utf-8'), accept_list)) errs[2] += 1 continue errs[3] += 1 # check PCR value has been found if not found_pcr: logger.error("IMA measurement list does not match TPM PCR %s" % pcrval) return None # clobber the retval if there were IMA file errors if sum(errs[:3]) > 0: logger.error("IMA ERRORS: template-hash %d fnf %d hash %d good %d" % tuple(errs)) return None return codecs.encode(runninghash, 'hex').decode('utf-8')
def _process_measurement_list(agentAttestState, lines, lists=None, m2w=None, pcrval=None, ima_keyring=None, boot_aggregates=None): running_hash = agentAttestState.get_pcr_state(config.IMA_PCR) found_pcr = (pcrval is None) errors = {} pcrval_bytes = b'' if pcrval is not None: pcrval_bytes = codecs.decode(pcrval.encode('utf-8'), 'hex') if lists is not None: if isinstance(lists, str): lists = ast.literal_eval(lists) allow_list = lists['allowlist'] exclude_list = lists['exclude'] else: allow_list = None exclude_list = None if boot_aggregates and allow_list: if 'boot_aggregate' not in allow_list['hashes']: allow_list['hashes']['boot_aggregate'] = [] for alg in boot_aggregates.keys(): for val in boot_aggregates[alg]: if val not in allow_list['hashes']['boot_aggregate']: allow_list['hashes']['boot_aggregate'].append(val) is_valid, compiled_regex, err_msg = config.valid_exclude_list(exclude_list) if not is_valid: # This should not happen as the exclude list has already been validated # by the verifier before acceping it. This is a safety net just in case. err_msg += " Exclude list will be ignored." logger.error(err_msg) ima_validator = ima_ast.Validator({ ima_ast.ImaSig: functools.partial(_validate_ima_sig, compiled_regex, ima_keyring, allow_list), ima_ast.ImaNg: functools.partial(_validate_ima_ng, compiled_regex, allow_list), ima_ast.Ima: functools.partial(_validate_ima_ng, compiled_regex, allow_list), ima_ast.ImaBuf: functools.partial(_validate_ima_buf, compiled_regex, allow_list), }) for linenum, line in enumerate(lines): line = line.strip() if line == '': continue try: entry = ima_ast.Entry(line, ima_validator) # update hash running_hash = hashlib.sha1(running_hash + entry.template_hash).digest() if not entry.valid(): errors[type(entry.mode)] = errors.get(type(entry.mode), 0) + 1 if not found_pcr: # End of list should equal pcr value found_pcr = (running_hash == pcrval_bytes) if found_pcr: logger.debug('Found match at linenum %s' % (linenum + 1)) # We always want to have the very last line for the attestation, so # we keep the previous runninghash, which is not the last one! agentAttestState.update_ima_attestation( int(entry.pcr), running_hash, linenum + 1) # Keep old functionality for writing the parsed files with hashes into a file if m2w is not None and (type(entry.mode) in [ ima_ast.Ima, ima_ast.ImaNg, ima_ast.ImaSig ]): hash_value = codecs.encode(entry.mode.digest.hash, "hex") path = entry.mode.path.name m2w.write(f"{hash_value} {path}\n") except ima_ast.ParserError: logger.error( f"Line was not parsable into a valid IMA entry: {line}") # iterative attestation may send us no log; compare last know PCR 10 state # against current PCR state if not found_pcr: found_pcr = (running_hash == pcrval_bytes) # check PCR value has been found if not found_pcr: logger.error("IMA measurement list does not match TPM PCR %s" % pcrval) return None # Check if any validators failed if sum(errors.values()) > 0: error_msg = "IMA ERRORS: Some entries couldn't be validated. Number of failures in modes: " error_msg += ", ".join( [f'{k.__name__ } {v}' for k, v in errors.items()]) logger.error(error_msg + ".") return None return codecs.encode(running_hash, 'hex').decode('utf-8')