diff options
| author | Stanislav Boboň | 2020-10-24 15:24:06 +0200 |
|---|---|---|
| committer | GitHub | 2020-10-24 15:24:06 +0200 |
| commit | 42de4e696a6a9a595142fb9139adbc0f916ccbbe (patch) | |
| tree | 9c714b7c0125efe913059a5aeda677b55a2325c5 | |
| parent | 47f4d9a6a8fd58fad0d9d2f76d004a6222933159 (diff) | |
| download | sec-certs-42de4e696a6a9a595142fb9139adbc0f916ccbbe.tar.gz sec-certs-42de4e696a6a9a595142fb9139adbc0f916ccbbe.tar.zst sec-certs-42de4e696a6a9a595142fb9139adbc0f916ccbbe.zip | |
Fips - bs4, added parsing (#9)
* fixed KeyErrors and added simplier table finding
* remove duplicities in json
* use bs4 to parse web files
* search for broken pdfs, saner pdf opening
* better html parsing
* more exceptions handled
* more fields to parse
* added more rules
* type hinting added
* common dict + Path functions
Co-authored-by: Stanislav Boboň <xbobon@fi.muni.cz>
| -rw-r--r-- | requirements.txt | 8 | ||||
| -rw-r--r-- | sec_certs/cert_rules.py | 48 | ||||
| -rw-r--r-- | sec_certs/extract_certificates.py | 4 | ||||
| -rwxr-xr-x | sec_certs/fips_certificates.py | 311 |
4 files changed, 206 insertions, 165 deletions
diff --git a/requirements.txt b/requirements.txt index cb1e0c9b..8b817605 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,4 +11,10 @@ python-dateutil==2.8.1 six==1.15.0 tabulate==0.8.7 pikepdf~=1.19.3 -tqdm==4.50.2
\ No newline at end of file +tqdm==4.50.2 +setuptools~=50.3.2 +requests~=2.24.0 +click~=7.1.2 +bs4~=0.0.1 +beautifulsoup4~=4.9.3 +tabula-py~=2.2.0
\ No newline at end of file diff --git a/sec_certs/cert_rules.py b/sec_certs/cert_rules.py index 78257525..2100a60b 100644 --- a/sec_certs/cert_rules.py +++ b/sec_certs/cert_rules.py @@ -323,6 +323,33 @@ rules_fips_htmls = [ r"Vendor<\/h4>[\s\S]*?href=\".*?\">(?P<fips_vendor>.*?)<\/a>" ] + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Common rules +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +common_rules = {} +common_rules['rules_os'] = rules_os +common_rules['rules_standard_id'] = rules_standard_id +common_rules['rules_security_level'] = rules_security_level +common_rules['rules_security_assurance_components'] = rules_security_assurance_components +common_rules['rules_security_functional_components'] = rules_security_functional_components +common_rules['rules_javacard'] = rules_javacard +common_rules['rules_crypto_algs'] = rules_crypto_algs +common_rules['rules_block_cipher_modes'] = rules_block_cipher_modes +common_rules['rules_ecc_curves'] = rules_ecc_curves +common_rules['rules_cplc'] = rules_cplc +common_rules['rules_crypto_engines'] = rules_crypto_engines +common_rules['rules_crypto_libs'] = rules_crypto_libs +common_rules['rules_IC_data_groups'] = rules_IC_data_groups +common_rules['rules_defenses'] = rules_defenses +common_rules['rules_certification_process'] = rules_certification_process +common_rules['rules_vulnerabilities'] = rules_vulnerabilities +common_rules['rules_other'] = rules_other + + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# For CC +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # rules_security_target_class rules = {} rules['rules_vendor'] = rules_vendor @@ -330,28 +357,13 @@ rules['rules_cert_id'] = rules_cert_id rules['rules_protection_profiles'] = rules_protection_profiles rules['rules_technical_reports'] = rules_technical_reports rules['rules_device_id'] = rules_device_id -rules['rules_os'] = rules_os -rules['rules_standard_id'] = rules_standard_id -rules['rules_security_level'] = rules_security_level -rules['rules_security_assurance_components'] = rules_security_assurance_components -rules['rules_security_functional_components'] = rules_security_functional_components -rules['rules_javacard'] = rules_javacard -rules['rules_crypto_algs'] = rules_crypto_algs -rules['rules_block_cipher_modes'] = rules_block_cipher_modes -rules['rules_ecc_curves'] = rules_ecc_curves -rules['rules_cplc'] = rules_cplc -rules['rules_crypto_engines'] = rules_crypto_engines -rules['rules_crypto_libs'] = rules_crypto_libs -rules['rules_IC_data_groups'] = rules_IC_data_groups -rules['rules_defenses'] = rules_defenses -rules['rules_certification_process'] = rules_certification_process -rules['rules_vulnerabilities'] = rules_vulnerabilities -rules['rules_other'] = rules_other +rules.update(common_rules) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# For FIPS +# For FIPS # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ fips_rules = {} fips_rules['rules_fips_algorithms'] = rules_fips_remove_algorithm_ids fips_rules['rules_security_level'] = rules_fips_security_level fips_rules['rules_cert_id'] = rules_fips_cert +fips_rules.update(common_rules)
\ No newline at end of file diff --git a/sec_certs/extract_certificates.py b/sec_certs/extract_certificates.py index a857a5c4..a86957c0 100644 --- a/sec_certs/extract_certificates.py +++ b/sec_certs/extract_certificates.py @@ -1077,10 +1077,10 @@ def extract_certificates_keywords(walk_dir: Path, fragments_dir: Path, file_pref # print('*** {} ***'.format(file_name)) - file_cert_name = os.path.splitext( + fips_cert_name = os.path.splitext( os.path.splitext(os.path.basename(file_name))[0])[0] # parse certificate, return all matches - all_items_found[file_name], modified_cert_file = parse_cert_file( + all_items_found[fips_cert_name if fips_items else file_name], modified_cert_file = parse_cert_file( file_name, fips_rules if fips_items else rules, -1, should_censure_right_away=should_censure_right_away, fips_items=fips_items) diff --git a/sec_certs/fips_certificates.py b/sec_certs/fips_certificates.py index 55c6a33b..a6a73bf0 100755 --- a/sec_certs/fips_certificates.py +++ b/sec_certs/fips_certificates.py @@ -4,24 +4,33 @@ import os import re import time from pathlib import Path -from typing import Set, Optional +from typing import Set, Optional, List, Dict +from bs4 import BeautifulSoup from graphviz import Digraph import click import pikepdf -# from camelot import read_pdf from tabula import read_pdf from .download import download_fips_web, download_fips from . import extract_certificates from .files import load_json_files, FILE_ERRORS_STRATEGY, search_files -from .cert_rules import rules_fips_htmls as RE_FIPS_HTMLS - FIPS_BASE_URL = 'https://csrc.nist.gov' FIPS_MODULE_URL = 'https://csrc.nist.gov/projects/cryptographic-module-validation-program/certificate/' +def find_empty_pdfs(base_dir: Path) -> (List, List): + missing = [] + not_available = [] + for i in range(1, 3725): + if not (base_dir / f'{i}.pdf').exists(): + missing.append(i) + elif os.path.getsize(base_dir / f'{i}.pdf') < 10000: + not_available.append(i) + return missing, not_available + + def extract_filename(file: str) -> str: """ Extracts filename from path @@ -31,147 +40,144 @@ def extract_filename(file: str) -> str: return os.path.splitext(os.path.basename(file))[0] -def parse_ul(text): - """ - Parses content between <ul> tags in FIPS .html CMVP page - :param text: text in <ul> tags - :return: all <li> elements - """ - p = re.compile(r"<li>(.*?)<\/li>") - return p.findall(text) - - -def parse_table(text): +def parse_table(element: BeautifulSoup) -> List[Dict]: """ Parses content of <table> tags in FIPS .html CMVP page - :param text: text in <table> tags + :param element: text in <table> tags :return: list of all found algorithm IDs """ - items_found_all = [] - - # find <tr>, in that look for "text-nowrap" and look if there is a cert mentioned - tr_pattern = re.compile(r"<tr>([\s\S]*?)<\/tr>") - name_pattern = re.compile(r"wrap\">(?P<name>[\s\S]*?)<\/td>") - cert_pattern_found = re.compile(r"<td>[ \S]*?#[ \S]*?\d+[ \S]*?<\/td>") - cert_pattern_localize = re.compile(r"#?[ \S]*?(?P<cert>\d+)") - - for tr_match in tr_pattern.finditer(text): - items_found = {} - current_tr = tr_match.group() - items_found['Name'] = name_pattern.search(current_tr).group('name') - cert_line = cert_pattern_found.search(current_tr) + found_items = [] + trs = element.find_all('tr') + for tr in trs: + tds = tr.find_all('td') + found_items.append({'Name': tds[0].text, 'Certificate': parse_algorithms(tds[1].text)}) - if cert_line is None: - items_found['Certificate'] = ['Not found'] - else: - items_found['Certificate'] = ['#' + x.group('cert') for x in cert_pattern_localize.finditer( - cert_line.group())] - - items_found_all.append(items_found) - - return items_found_all + return found_items -def parse_algorithms(text, in_pdf=False): +def parse_algorithms(text: str, in_pdf: bool = False) -> List: """ Parses table of FIPS (non) allowed algorithms :param text: Contents of the table :param in_pdf: Specifies whether the table was found in a PDF security policies file :return: list of all found algorithm IDs """ - items_found = [] - for m in re.finditer(rf"(?:#{'?' if in_pdf else ''}\s?|Cert\.?[^. ]*?\s?)(?:[Cc]\s)?(?P<id>\d+)", text): - items_found.append({'Certificate': m.group()}) + set_items = set() + for m in re.finditer(rf"(?:#{'?' if in_pdf else 'C?'}\s?|Cert\.?[^. ]*?\s?)(?:[Cc]\s)?(?P<id>\d+)", text): + set_items.add(m.group()) - return items_found + return list(set_items) -def parse_caveat(text): +def parse_caveat(text: str) -> List: """ Parses content of "Caveat" of FIPS CMVP .html file :param text: text of "Caveat" :return: list of all found algorithm IDs """ items_found = [] - - for m in re.finditer(r"(?:#\s?|Cert\.?(?!.\s)\s?|Certificate\s?)(?P<id>\d+)", text): - items_found.append({r"(?:#\s?|Cert\.?(?!.\s)\s?|Certificate\s?)(?P<id>\d+?})": {m.group(): {'count': 1}}}) + r_key = r"(?:#\s?|Cert\.?(?!.\s)\s?|Certificate\s?)(?P<id>\d+)" + for m in re.finditer(r_key, text): + if r_key in items_found and m.group() in items_found[0]: + items_found[0][m.group()]['count'] += 1 + else: + items_found.append({r"(?:#\s?|Cert\.?(?!.\s)\s?|Certificate\s?)(?P<id>\d+?})": {m.group(): {'count': 1}}}) return items_found -def initialize_entry(input_dictionary): +def initialize_entry(input_dictionary: Dict): """ - Initialize input dictionary with elements that shuold be always processed - :param input_dictionary: dictionary used as "all_items" + Initialize input dictionary with elements that should be always processed + :param input_dictionary: empty dictionary used as "all_items" """ input_dictionary['fips_exceptions'] = [] input_dictionary['fips_tested_conf'] = [] + input_dictionary['fips_mentioned_certs'] = [] input_dictionary['fips_algorithms'] = [] input_dictionary['fips_caveat'] = [] input_dictionary['tables_done'] = False + input_dictionary['fips_module_name'] = '' -def fips_search_html(base_dir, output_file, dump_to_file=False): - """fips_search_html. - - :param base_dir: directory to search for html files - :param output_file: file to dump json to - :param dump_to_file: True/False - """ - +def fips_search_html(base_dir: Path, output_file: str, dump_to_file: bool = False) -> Dict: all_found_items = {} + pairs = { + 'Module Name': 'fips_module_name', + 'Standard': 'fips_standard', + 'Status': 'fips_status', + 'Sunset Date': 'fips_date_sunset', + 'Validation Dates': 'fips_date_validation', + 'Overall Level': 'fips_level', + 'Caveat': 'fips_caveat', + 'Security Level Exceptions': 'fips_exceptions', + 'Module Type': 'fips_type', + 'Embodiment': 'fips_embodiment', + 'FIPS Algorithms': 'fips_algorithms', + 'Allowed Algorithms': 'fips_algorithms', + 'Other Algorithms': 'fips_algorithms', + 'Tested Configuration(s)': 'fips_tested_conf', + 'Description': 'fips_description' + } for file in search_files(base_dir): - items_found = {} - initialize_entry(items_found) + current_items_found = {'cert_fips_id': extract_filename(file)} + all_found_items[extract_filename(file)] = current_items_found + initialize_entry(current_items_found) text = extract_certificates.load_cert_html_file(file) - filename = os.path.splitext(os.path.basename(file))[0] - all_found_items[filename] = items_found - items_found['cert_fips_id'] = filename + soup = BeautifulSoup(text, 'html.parser') + for div in soup.find_all('div', class_='row padrow'): + title = div.find('div', class_='col-md-3').text.strip() + content = div.find('div', class_='col-md-9').text.strip() \ + .replace('\n', '').replace('\t', '').replace(' ', ' ') - for rule in RE_FIPS_HTMLS: - m = re.search(rule, text) - if m is None: - continue + if title in pairs: + if 'date' in pairs[title]: + current_items_found[pairs[title]] = content.split(';') + elif 'caveat' in pairs[title]: + current_items_found[pairs[title]] = content + current_items_found['fips_mentioned_certs'] += parse_caveat(content) - group_dict = m.groupdict() - key = list(group_dict) + elif 'FIPS Algorithms' in title: + current_items_found['fips_algorithms'] += parse_table(div.find('div', class_='col-md-9')) - # <ul> - if key[0] == 'fips_exceptions' or key[0] == 'fips_tested_conf': - items_found[key[0]] = parse_ul(group_dict[key[0]]) + elif 'Algorithms' in title: + current_items_found['fips_algorithms'] += [{'Certificate': x} for x in parse_algorithms(content)] - # <table> - elif key[0] == 'fips_algorithms': - if 'fips_algorithms' not in items_found: - items_found['fips_algorithms'] = parse_table(group_dict[key[0]]) + elif 'tested_conf' in pairs[title]: + current_items_found[pairs[title]] = [x.text for x in + div.find('div', class_='col-md-9').find_all('li')] else: - items_found['fips_algorithms'] += parse_table( - group_dict[key[0]]) + current_items_found[pairs[title]] = content - # allowed algorithms - elif key[0] == 'fips_allowed_algorithms': - if 'fips_algorithms' not in items_found: - items_found['fips_algorithms'] = parse_algorithms( - group_dict[key[0]]) + for div in soup.find_all('div', class_='panel panel-default')[1:]: + if div.find('h4', class_='panel-title').text == 'Vendor': + vendor_string = div.find('div', 'panel-body').find('a') + if not vendor_string: + vendor_string = list(div.find('div', 'panel-body').children)[0].strip() + current_items_found['fips_vendor_www'] = '' else: - items_found['fips_algorithms'] += parse_algorithms( - group_dict[key[0]]) - - # certificates in Caveat - elif key[0] == 'fips_caveat': - items_found['fips_mentioned_certs'] = parse_caveat(group_dict[key[0]]) + current_items_found['fips_vendor_www'] = vendor_string.get('href') + vendor_string = vendor_string.text.strip() + current_items_found['fips_vendor'] = vendor_string + if current_items_found['fips_vendor'] == '': + print("WARNING: NO VENDOR FOUND", file) - # there are usually multiple dates separated by ";" - elif 'date' in key[0]: - items_found[key[0]] = group_dict[key[0]].replace('\n', '').replace( - '\t', '').replace(' ', ' ').strip().split(';') + if div.find('h4', class_='panel-title').text == 'Lab': + current_items_found['fips_lab'] = list(div.find('div', 'panel-body').children)[0].strip() + current_items_found['fips_nvlap_code'] = \ + list(div.find('div', 'panel-body').children)[2].strip().split('\n')[1].strip() + if current_items_found['fips_lab'] == '': + print("WARNING: NO LAB FOUND", file) + if current_items_found['fips_nvlap_code'] == '': + print("WARNING: NO NVLAP CODE FOUND", file) - else: - items_found[key[0]] = group_dict[key[0]].replace( - '\n', '').replace('\t', '').replace(' ', ' ').strip() + if div.find('h4', class_='panel-title').text == 'Related Files': + links = div.find_all('a') + current_items_found['fips_security_policy_www'] = FIPS_BASE_URL + links[0].get('href') + if len(links) == 2: + current_items_found['fips_certificate_www'] = FIPS_BASE_URL + links[1].get('href') if dump_to_file: with open(output_file, 'w', errors=FILE_ERRORS_STRATEGY) as write_file: @@ -180,7 +186,7 @@ def fips_search_html(base_dir, output_file, dump_to_file=False): return all_found_items -def get_dot_graph(found_items, output_file_name): +def get_dot_graph(found_items: Dict, output_file_name: str): """ Function that plots .dot graph of dependencies between certificates Certificates with at least one dependency are displayed in "{output_file_name}connections.pdf", remaining @@ -240,8 +246,8 @@ def get_dot_graph(found_items, output_file_name): print(f"rendering {keys} keys and {edges} edges") - dot.render(output_file_name + 'connections', view=True) - single_dot.render(output_file_name + 'single', view=True) + dot.render(str(output_file_name) + '_connections', view=True) + single_dot.render(str(output_file_name) + '_single', view=True) def remove_algorithms_from_extracted_data(items, html): @@ -253,7 +259,7 @@ def remove_algorithms_from_extracted_data(items, html): for file_name in items: items[file_name]['file_status'] = True html[file_name]['file_status'] = True - if 'fips_mentioned_certs' in html[file_name]: + if html[file_name]['fips_mentioned_certs']: for item in html[file_name]['fips_mentioned_certs']: items[file_name]['rules_cert_id'].update(item) @@ -273,7 +279,7 @@ def remove_algorithms_from_extracted_data(items, html): html[file_name]['cert_fips_id'], None) -def validate_results(items, html): +def validate_results(items: Dict, html: Dict): """ Function that validates results and finds the final connection output :param items: All keyword items found in pdf files @@ -286,15 +292,17 @@ def validate_results(items, html): cert_id = ''.join(filter(str.isdigit, cert)) if cert_id == '' or cert_id not in html: + # TEST + # if cert_id == '' or int(cert_id) > 3730: broken_files.add(file_name) items[file_name]['file_status'] = False html[file_name]['file_status'] = False break - - print("WARNING: CERTIFICATE FILES WITH WRONG CERTIFICATES PARSED") - print(*sorted(list(broken_files)), sep='\n') - print("... skipping these...") - print("Total non-analyzable files:", len(broken_files)) + if broken_files: + print("WARNING: CERTIFICATE FILES WITH WRONG CERTIFICATES PARSED") + print(*sorted(list(broken_files)), sep='\n') + print("... skipping these...") + print("Total non-analyzable files:", len(broken_files)) for file_name in items: html[file_name]['Connections'] = [] @@ -345,27 +353,20 @@ def extract_page_number(txt: str) -> Optional[str]: return m[-1][-1] if m else None -def find_tables(txt, file_name, num_pages): - """ - Function that tries to pages in security policy pdf files, where it's possible to find a table containing - algorithms - :param txt: file in .txt format (output of pdftotext) - :param file_name: name of the file - :param num_pages: number of pages in pdf - :return: list of pages possibly containing a table - None if these cannot be found - """ - # Look for "List of Tables", where we can find exactly tables with page num - tables_regex = re.compile(r"^(?:(?:[Tt]able\s|[Ll]ist\s)(?:[Oo]f\s))[Tt]ables[\s\S]+?\f", re.MULTILINE) - table = tables_regex.search(txt) - if table: - rb = parse_list_of_tables(table.group()) - if rb: - return list(rb) - return None +def find_tables_iterative(file_text: str) -> List[int]: + current_page = 1 + pages = set() + for line in file_text.split('\n'): + if '\f' in line: + current_page += 1 + if line.startswith('Table ') or line.startswith('Exhibit'): + pages.add(current_page) + if not pages: + print('~' * 20, 'No pages found', '~' * 20) + return list(pages) - # Otherwise look for "Table" in text and \f representing footer, then extract page number from footer - print("~" * 20, file_name, '~' * 20) + +def find_footers(txt: str, num_pages: int) -> Optional[List]: footer_regex = re.compile( r"(?:Table[^\f]*)(?P<first>^[\S\t ]*$)\n(?P<second>(\f[ \t\S]+)$)(?P<third>\n^[ \t\S]+?$)?", re.MULTILINE) @@ -390,7 +391,31 @@ def find_tables(txt, file_name, num_pages): return footers -def repair_pdf_page_count(file: str) -> int: +def find_tables(txt: str, file_name: Path) -> Optional[List]: + """ + Function that tries to pages in security policy pdf files, where it's possible to find a table containing + algorithms + :param txt: file in .txt format (output of pdftotext) + :param file_name: name of the file + :return: list of pages possibly containing a table + None if these cannot be found + """ + # Look for "List of Tables", where we can find exactly tables with page num + tables_regex = re.compile(r"^(?:(?:[Tt]able\s|[Ll]ist\s)(?:[Oo]f\s))[Tt]ables[\s\S]+?\f", re.MULTILINE) + table = tables_regex.search(txt) + if table: + rb = parse_list_of_tables(table.group()) + if rb: + return list(rb) + return None + + # Otherwise look for "Table" in text and \f representing footer, then extract page number from footer + print("~" * 20, file_name, '~' * 20) + rb = find_tables_iterative(txt) + return rb if rb else None + + +def repair_pdf(file: Path): """ Some pdfs can't be opened by PyPDF2 - opening them with pikepdf and then saving them fixes this issue. By opening this file in a pdf reader, we can already extract number of pages @@ -399,10 +424,9 @@ def repair_pdf_page_count(file: str) -> int: """ pdf = pikepdf.Pdf.open(file, allow_overwriting_input=True) pdf.save(file) - return len(pdf.pages) -def extract_certs_from_tables(list_of_files, html_items): +def extract_certs_from_tables(list_of_files: List, html_items: Dict) -> List[Path]: """ Function that extracts algorithm IDs from tables in security policies files. :param list_of_files: iterable containing all files to parse @@ -418,23 +442,22 @@ def extract_certs_from_tables(list_of_files, html_items): continue with open(cert_file, 'r') as f: - try: - pages = repair_pdf_page_count(cert_file[:-4]) - except pikepdf._qpdf.PdfError: - not_decoded.append(cert_file) - continue - tables = find_tables(f.read(), cert_file, pages) + tables = find_tables(f.read(), cert_file) # If we find any tables with page numbers, we process them if tables: lst = [] print("~~~~~~~~~~~~~~~", cert_file, "~~~~~~~~~~~~~~~~~~~~~~~") - try: data = read_pdf(cert_file[:-4], pages=tables, silent=True) except Exception: - not_decoded.append(cert_file) - continue + try: + repair_pdf(cert_file[:-4]) + data = read_pdf(cert_file[:-4], pages=tables, silent=True) + + except Exception: + not_decoded.append(cert_file) + continue # find columns with cert numbers for df in data: @@ -444,11 +467,9 @@ def extract_certs_from_tables(list_of_files, html_items): # Parse again if someone picks not so descriptive column names lst += parse_algorithms(df.to_string(index=False)) + if lst: - if 'fips_algorithms' not in html_items[extract_filename(cert_file[:-8])]: - html_items[extract_filename(cert_file[:-8])]['fips_algorithms'] = lst - else: - html_items[extract_filename(cert_file[:-8])]['fips_algorithms'] += lst + html_items[extract_filename(cert_file[:-8])]['fips_algorithms'] += lst html_items[extract_filename(cert_file[:-8])]['tables_done'] = True return not_decoded @@ -479,7 +500,9 @@ def main(directory, do_download_meta: bool, do_download_certs: bool, threads: in if do_download_certs: download_fips(web_dir, policies_dir, threads) - + missing, not_available = find_empty_pdfs(policies_dir) + print(f"Missing security policies: Total {len(missing)}") + print(f"Not available security policies: Total {len(not_available)}") files_to_load = [ results_dir / 'fips_data_keywords_all.json', results_dir / 'fips_html_all.json' @@ -515,7 +538,7 @@ def main(directory, do_download_meta: bool, do_download_certs: bool, threads: in with open(results_dir / 'fips_html_all.json', 'w') as f: json.dump(html, f, indent=4, sort_keys=True) print("PLOTTING GRAPH") - get_dot_graph(html, 'output') + get_dot_graph(html, results_dir / 'output') end = time.time() print("TIME:", end - start) |
