aboutsummaryrefslogtreecommitdiffhomepage
path: root/sec_certs/dataset/fips_algorithm.py
blob: e505b217f08e83ae138fce73d7ac2ee95a86d4c8 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import json
import logging
from pathlib import Path
from typing import Dict, List, Union

from bs4 import BeautifulSoup

from sec_certs import constants as constants
from sec_certs import helpers as helpers
from sec_certs import parallel_processing as cert_processing
from sec_certs.config.configuration import config
from sec_certs.dataset.dataset import Dataset
from sec_certs.sample.fips import FIPSCertificate
from sec_certs.serialization.json import ComplexSerializableType, CustomJSONDecoder, CustomJSONEncoder

logger = logging.getLogger(__name__)


class FIPSAlgorithmDataset(Dataset, ComplexSerializableType):

    certs: Dict[str, List]  # type: ignore # noqa

    def get_certs_from_web(self):
        self.root_dir.mkdir(exist_ok=True)
        algs_paths, algs_urls = [], []

        # get first page to find out how many pages there are
        helpers.download_file(constants.FIPS_ALG_URL + "1", self.root_dir / "page1.html")

        with open(self.root_dir / "page1.html", "r") as alg_file:
            soup = BeautifulSoup(alg_file.read(), "html.parser")
            num_pages = soup.select("span[data-total-pages]")[0].attrs

        for i in range(2, int(num_pages["data-total-pages"]) + 1):
            if not (self.root_dir / f"page{i}.html").exists():
                algs_urls.append(constants.FIPS_ALG_URL + str(i))
                algs_paths.append(self.root_dir / f"page{i}.html")

        # get the last page, always
        helpers.download_file(
            constants.FIPS_ALG_URL + num_pages["data-total-pages"],
            self.root_dir / f"page{int(num_pages['data-total-pages'])}.html",
        )
        logger.info(f"downloading {len(algs_urls)} algs html files")
        cert_processing.process_parallel(
            FIPSCertificate.download_html_page, list(zip(algs_urls, algs_paths)), config.n_threads
        )

        self.parse_html()

    @staticmethod
    def _extract_algorithm_information(elements, vendor, date, product, validation):
        for elem in elements:
            # td > a > (vendor or date)
            attachments = elem.find_all("a")

            if len(attachments) == 0:
                vendor = elem.text.strip() if "vendor-name" in elem["id"] else vendor
                date = elem.text.strip() if "validation-date" in elem["id"] else date
                continue

            for attachment in attachments:
                product = elem.text.strip() if "product-name" in attachment["id"] else product
                validation = elem.text.strip() if "validation-number" in attachment["id"] else validation
        return vendor, date, product, validation

    def parse_html(self):
        def split_alg(alg_string):
            cert_type = alg_string.rstrip("0123456789")
            cert_id = alg_string[len(cert_type) :]
            return cert_type.strip(), cert_id.strip()

        for f in helpers.search_files(self.root_dir):
            if not f.endswith("html"):
                continue

            with open(f, "r", encoding="utf-8") as handle:
                html_soup = BeautifulSoup(handle.read(), "html.parser")

            table = html_soup.find("table", class_="table table-condensed publications-table table-bordered")
            tbody_contents = table.find("tbody").find_all("tr")
            vendor = product = validation = date = ""
            for tr in tbody_contents:
                elements = tr.find_all("td")
                vendor, date, product, validation = FIPSAlgorithmDataset._extract_algorithm_information(
                    elements, vendor, date, product, validation
                )

                alg_type, alg_id = split_alg(validation)
                fips_alg = FIPSCertificate.Algorithm(alg_id, vendor, product, alg_type, date)
                if alg_id not in self.certs:
                    self.certs[alg_id] = []
                self.certs[alg_id].append(fips_alg)

    def convert_all_pdfs(self):
        raise NotImplementedError("Not meant to be implemented")

    def download_all_pdfs(self):
        raise NotImplementedError("Not meant to be implemented")

    @property
    def serialized_attributes(self) -> List[str]:
        return ["certs"]

    @classmethod
    def from_dict(cls, dct: Dict):
        certs = dct["certs"]

        directory = dct["_root_dir"] if "_root_dir" in dct else ""
        dset = cls(certs, Path(directory), "algorithms", "algorithms used in dataset")
        return dset

    def to_dict(self):
        return self.__dict__

    def to_json(self, output_path: Union[str, Path] = None):
        if not output_path:
            output_path = self.json_path
        with Path(output_path).open("w") as handle:
            json.dump(self, handle, indent=4, cls=CustomJSONEncoder)

    @classmethod
    def from_json(cls, input_path: Union[str, Path]):
        input_path = Path(input_path)
        with input_path.open("r") as handle:
            dset = json.load(handle, cls=CustomJSONDecoder)
        dset.root_dir = input_path.parent.absolute()
        return dset