xref: /aosp_15_r20/external/mbedtls/tests/scripts/audit-validity-dates.py (revision 62c56f9862f102b96d72393aff6076c951fb8148)
1*62c56f98SSadaf Ebrahimi#!/usr/bin/env python3
2*62c56f98SSadaf Ebrahimi#
3*62c56f98SSadaf Ebrahimi# Copyright The Mbed TLS Contributors
4*62c56f98SSadaf Ebrahimi# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
5*62c56f98SSadaf Ebrahimi
6*62c56f98SSadaf Ebrahimi"""Audit validity date of X509 crt/crl/csr.
7*62c56f98SSadaf Ebrahimi
8*62c56f98SSadaf EbrahimiThis script is used to audit the validity date of crt/crl/csr used for testing.
9*62c56f98SSadaf EbrahimiIt prints the information about X.509 objects excluding the objects that
10*62c56f98SSadaf Ebrahimiare valid throughout the desired validity period. The data are collected
11*62c56f98SSadaf Ebrahimifrom tests/data_files/ and tests/suites/*.data files by default.
12*62c56f98SSadaf Ebrahimi"""
13*62c56f98SSadaf Ebrahimi
14*62c56f98SSadaf Ebrahimiimport os
15*62c56f98SSadaf Ebrahimiimport re
16*62c56f98SSadaf Ebrahimiimport typing
17*62c56f98SSadaf Ebrahimiimport argparse
18*62c56f98SSadaf Ebrahimiimport datetime
19*62c56f98SSadaf Ebrahimiimport glob
20*62c56f98SSadaf Ebrahimiimport logging
21*62c56f98SSadaf Ebrahimiimport hashlib
22*62c56f98SSadaf Ebrahimifrom enum import Enum
23*62c56f98SSadaf Ebrahimi
24*62c56f98SSadaf Ebrahimi# The script requires cryptography >= 35.0.0 which is only available
25*62c56f98SSadaf Ebrahimi# for Python >= 3.6.
26*62c56f98SSadaf Ebrahimiimport cryptography
27*62c56f98SSadaf Ebrahimifrom cryptography import x509
28*62c56f98SSadaf Ebrahimi
29*62c56f98SSadaf Ebrahimifrom generate_test_code import FileWrapper
30*62c56f98SSadaf Ebrahimi
31*62c56f98SSadaf Ebrahimiimport scripts_path # pylint: disable=unused-import
32*62c56f98SSadaf Ebrahimifrom mbedtls_dev import build_tree
33*62c56f98SSadaf Ebrahimifrom mbedtls_dev import logging_util
34*62c56f98SSadaf Ebrahimi
35*62c56f98SSadaf Ebrahimidef check_cryptography_version():
36*62c56f98SSadaf Ebrahimi    match = re.match(r'^[0-9]+', cryptography.__version__)
37*62c56f98SSadaf Ebrahimi    if match is None or int(match.group(0)) < 35:
38*62c56f98SSadaf Ebrahimi        raise Exception("audit-validity-dates requires cryptography >= 35.0.0"
39*62c56f98SSadaf Ebrahimi                        + "({} is too old)".format(cryptography.__version__))
40*62c56f98SSadaf Ebrahimi
41*62c56f98SSadaf Ebrahimiclass DataType(Enum):
42*62c56f98SSadaf Ebrahimi    CRT = 1 # Certificate
43*62c56f98SSadaf Ebrahimi    CRL = 2 # Certificate Revocation List
44*62c56f98SSadaf Ebrahimi    CSR = 3 # Certificate Signing Request
45*62c56f98SSadaf Ebrahimi
46*62c56f98SSadaf Ebrahimi
47*62c56f98SSadaf Ebrahimiclass DataFormat(Enum):
48*62c56f98SSadaf Ebrahimi    PEM = 1 # Privacy-Enhanced Mail
49*62c56f98SSadaf Ebrahimi    DER = 2 # Distinguished Encoding Rules
50*62c56f98SSadaf Ebrahimi
51*62c56f98SSadaf Ebrahimi
52*62c56f98SSadaf Ebrahimiclass AuditData:
53*62c56f98SSadaf Ebrahimi    """Store data location, type and validity period of X.509 objects."""
54*62c56f98SSadaf Ebrahimi    #pylint: disable=too-few-public-methods
55*62c56f98SSadaf Ebrahimi    def __init__(self, data_type: DataType, x509_obj):
56*62c56f98SSadaf Ebrahimi        self.data_type = data_type
57*62c56f98SSadaf Ebrahimi        # the locations that the x509 object could be found
58*62c56f98SSadaf Ebrahimi        self.locations = [] # type: typing.List[str]
59*62c56f98SSadaf Ebrahimi        self.fill_validity_duration(x509_obj)
60*62c56f98SSadaf Ebrahimi        self._obj = x509_obj
61*62c56f98SSadaf Ebrahimi        encoding = cryptography.hazmat.primitives.serialization.Encoding.DER
62*62c56f98SSadaf Ebrahimi        self._identifier = hashlib.sha1(self._obj.public_bytes(encoding)).hexdigest()
63*62c56f98SSadaf Ebrahimi
64*62c56f98SSadaf Ebrahimi    @property
65*62c56f98SSadaf Ebrahimi    def identifier(self):
66*62c56f98SSadaf Ebrahimi        """
67*62c56f98SSadaf Ebrahimi        Identifier of the underlying X.509 object, which is consistent across
68*62c56f98SSadaf Ebrahimi        different runs.
69*62c56f98SSadaf Ebrahimi        """
70*62c56f98SSadaf Ebrahimi        return self._identifier
71*62c56f98SSadaf Ebrahimi
72*62c56f98SSadaf Ebrahimi    def fill_validity_duration(self, x509_obj):
73*62c56f98SSadaf Ebrahimi        """Read validity period from an X.509 object."""
74*62c56f98SSadaf Ebrahimi        # Certificate expires after "not_valid_after"
75*62c56f98SSadaf Ebrahimi        # Certificate is invalid before "not_valid_before"
76*62c56f98SSadaf Ebrahimi        if self.data_type == DataType.CRT:
77*62c56f98SSadaf Ebrahimi            self.not_valid_after = x509_obj.not_valid_after
78*62c56f98SSadaf Ebrahimi            self.not_valid_before = x509_obj.not_valid_before
79*62c56f98SSadaf Ebrahimi        # CertificateRevocationList expires after "next_update"
80*62c56f98SSadaf Ebrahimi        # CertificateRevocationList is invalid before "last_update"
81*62c56f98SSadaf Ebrahimi        elif self.data_type == DataType.CRL:
82*62c56f98SSadaf Ebrahimi            self.not_valid_after = x509_obj.next_update
83*62c56f98SSadaf Ebrahimi            self.not_valid_before = x509_obj.last_update
84*62c56f98SSadaf Ebrahimi        # CertificateSigningRequest is always valid.
85*62c56f98SSadaf Ebrahimi        elif self.data_type == DataType.CSR:
86*62c56f98SSadaf Ebrahimi            self.not_valid_after = datetime.datetime.max
87*62c56f98SSadaf Ebrahimi            self.not_valid_before = datetime.datetime.min
88*62c56f98SSadaf Ebrahimi        else:
89*62c56f98SSadaf Ebrahimi            raise ValueError("Unsupported file_type: {}".format(self.data_type))
90*62c56f98SSadaf Ebrahimi
91*62c56f98SSadaf Ebrahimi
92*62c56f98SSadaf Ebrahimiclass X509Parser:
93*62c56f98SSadaf Ebrahimi    """A parser class to parse crt/crl/csr file or data in PEM/DER format."""
94*62c56f98SSadaf Ebrahimi    PEM_REGEX = br'-{5}BEGIN (?P<type>.*?)-{5}(?P<data>.*?)-{5}END (?P=type)-{5}'
95*62c56f98SSadaf Ebrahimi    PEM_TAG_REGEX = br'-{5}BEGIN (?P<type>.*?)-{5}\n'
96*62c56f98SSadaf Ebrahimi    PEM_TAGS = {
97*62c56f98SSadaf Ebrahimi        DataType.CRT: 'CERTIFICATE',
98*62c56f98SSadaf Ebrahimi        DataType.CRL: 'X509 CRL',
99*62c56f98SSadaf Ebrahimi        DataType.CSR: 'CERTIFICATE REQUEST'
100*62c56f98SSadaf Ebrahimi    }
101*62c56f98SSadaf Ebrahimi
102*62c56f98SSadaf Ebrahimi    def __init__(self,
103*62c56f98SSadaf Ebrahimi                 backends:
104*62c56f98SSadaf Ebrahimi                 typing.Dict[DataType,
105*62c56f98SSadaf Ebrahimi                             typing.Dict[DataFormat,
106*62c56f98SSadaf Ebrahimi                                         typing.Callable[[bytes], object]]]) \
107*62c56f98SSadaf Ebrahimi    -> None:
108*62c56f98SSadaf Ebrahimi        self.backends = backends
109*62c56f98SSadaf Ebrahimi        self.__generate_parsers()
110*62c56f98SSadaf Ebrahimi
111*62c56f98SSadaf Ebrahimi    def __generate_parser(self, data_type: DataType):
112*62c56f98SSadaf Ebrahimi        """Parser generator for a specific DataType"""
113*62c56f98SSadaf Ebrahimi        tag = self.PEM_TAGS[data_type]
114*62c56f98SSadaf Ebrahimi        pem_loader = self.backends[data_type][DataFormat.PEM]
115*62c56f98SSadaf Ebrahimi        der_loader = self.backends[data_type][DataFormat.DER]
116*62c56f98SSadaf Ebrahimi        def wrapper(data: bytes):
117*62c56f98SSadaf Ebrahimi            pem_type = X509Parser.pem_data_type(data)
118*62c56f98SSadaf Ebrahimi            # It is in PEM format with target tag
119*62c56f98SSadaf Ebrahimi            if pem_type == tag:
120*62c56f98SSadaf Ebrahimi                return pem_loader(data)
121*62c56f98SSadaf Ebrahimi            # It is in PEM format without target tag
122*62c56f98SSadaf Ebrahimi            if pem_type:
123*62c56f98SSadaf Ebrahimi                return None
124*62c56f98SSadaf Ebrahimi            # It might be in DER format
125*62c56f98SSadaf Ebrahimi            try:
126*62c56f98SSadaf Ebrahimi                result = der_loader(data)
127*62c56f98SSadaf Ebrahimi            except ValueError:
128*62c56f98SSadaf Ebrahimi                result = None
129*62c56f98SSadaf Ebrahimi            return result
130*62c56f98SSadaf Ebrahimi        wrapper.__name__ = "{}.parser[{}]".format(type(self).__name__, tag)
131*62c56f98SSadaf Ebrahimi        return wrapper
132*62c56f98SSadaf Ebrahimi
133*62c56f98SSadaf Ebrahimi    def __generate_parsers(self):
134*62c56f98SSadaf Ebrahimi        """Generate parsers for all support DataType"""
135*62c56f98SSadaf Ebrahimi        self.parsers = {}
136*62c56f98SSadaf Ebrahimi        for data_type, _ in self.PEM_TAGS.items():
137*62c56f98SSadaf Ebrahimi            self.parsers[data_type] = self.__generate_parser(data_type)
138*62c56f98SSadaf Ebrahimi
139*62c56f98SSadaf Ebrahimi    def __getitem__(self, item):
140*62c56f98SSadaf Ebrahimi        return self.parsers[item]
141*62c56f98SSadaf Ebrahimi
142*62c56f98SSadaf Ebrahimi    @staticmethod
143*62c56f98SSadaf Ebrahimi    def pem_data_type(data: bytes) -> typing.Optional[str]:
144*62c56f98SSadaf Ebrahimi        """Get the tag from the data in PEM format
145*62c56f98SSadaf Ebrahimi
146*62c56f98SSadaf Ebrahimi        :param data: data to be checked in binary mode.
147*62c56f98SSadaf Ebrahimi        :return: PEM tag or "" when no tag detected.
148*62c56f98SSadaf Ebrahimi        """
149*62c56f98SSadaf Ebrahimi        m = re.search(X509Parser.PEM_TAG_REGEX, data)
150*62c56f98SSadaf Ebrahimi        if m is not None:
151*62c56f98SSadaf Ebrahimi            return m.group('type').decode('UTF-8')
152*62c56f98SSadaf Ebrahimi        else:
153*62c56f98SSadaf Ebrahimi            return None
154*62c56f98SSadaf Ebrahimi
155*62c56f98SSadaf Ebrahimi    @staticmethod
156*62c56f98SSadaf Ebrahimi    def check_hex_string(hex_str: str) -> bool:
157*62c56f98SSadaf Ebrahimi        """Check if the hex string is possibly DER data."""
158*62c56f98SSadaf Ebrahimi        hex_len = len(hex_str)
159*62c56f98SSadaf Ebrahimi        # At least 6 hex char for 3 bytes: Type + Length + Content
160*62c56f98SSadaf Ebrahimi        if hex_len < 6:
161*62c56f98SSadaf Ebrahimi            return False
162*62c56f98SSadaf Ebrahimi        # Check if Type (1 byte) is SEQUENCE.
163*62c56f98SSadaf Ebrahimi        if hex_str[0:2] != '30':
164*62c56f98SSadaf Ebrahimi            return False
165*62c56f98SSadaf Ebrahimi        # Check LENGTH (1 byte) value
166*62c56f98SSadaf Ebrahimi        content_len = int(hex_str[2:4], base=16)
167*62c56f98SSadaf Ebrahimi        consumed = 4
168*62c56f98SSadaf Ebrahimi        if content_len in (128, 255):
169*62c56f98SSadaf Ebrahimi            # Indefinite or Reserved
170*62c56f98SSadaf Ebrahimi            return False
171*62c56f98SSadaf Ebrahimi        elif content_len > 127:
172*62c56f98SSadaf Ebrahimi            # Definite, Long
173*62c56f98SSadaf Ebrahimi            length_len = (content_len - 128) * 2
174*62c56f98SSadaf Ebrahimi            content_len = int(hex_str[consumed:consumed+length_len], base=16)
175*62c56f98SSadaf Ebrahimi            consumed += length_len
176*62c56f98SSadaf Ebrahimi        # Check LENGTH
177*62c56f98SSadaf Ebrahimi        if hex_len != content_len * 2 + consumed:
178*62c56f98SSadaf Ebrahimi            return False
179*62c56f98SSadaf Ebrahimi        return True
180*62c56f98SSadaf Ebrahimi
181*62c56f98SSadaf Ebrahimi
182*62c56f98SSadaf Ebrahimiclass Auditor:
183*62c56f98SSadaf Ebrahimi    """
184*62c56f98SSadaf Ebrahimi    A base class that uses X509Parser to parse files to a list of AuditData.
185*62c56f98SSadaf Ebrahimi
186*62c56f98SSadaf Ebrahimi    A subclass must implement the following methods:
187*62c56f98SSadaf Ebrahimi      - collect_default_files: Return a list of file names that are defaultly
188*62c56f98SSadaf Ebrahimi        used for parsing (auditing). The list will be stored in
189*62c56f98SSadaf Ebrahimi        Auditor.default_files.
190*62c56f98SSadaf Ebrahimi      - parse_file: Method that parses a single file to a list of AuditData.
191*62c56f98SSadaf Ebrahimi
192*62c56f98SSadaf Ebrahimi    A subclass may override the following methods:
193*62c56f98SSadaf Ebrahimi      - parse_bytes: Defaultly, it parses `bytes` that contains only one valid
194*62c56f98SSadaf Ebrahimi        X.509 data(DER/PEM format) to an X.509 object.
195*62c56f98SSadaf Ebrahimi      - walk_all: Defaultly, it iterates over all the files in the provided
196*62c56f98SSadaf Ebrahimi        file name list, calls `parse_file` for each file and stores the results
197*62c56f98SSadaf Ebrahimi        by extending the `results` passed to the function.
198*62c56f98SSadaf Ebrahimi    """
199*62c56f98SSadaf Ebrahimi    def __init__(self, logger):
200*62c56f98SSadaf Ebrahimi        self.logger = logger
201*62c56f98SSadaf Ebrahimi        self.default_files = self.collect_default_files()
202*62c56f98SSadaf Ebrahimi        self.parser = X509Parser({
203*62c56f98SSadaf Ebrahimi            DataType.CRT: {
204*62c56f98SSadaf Ebrahimi                DataFormat.PEM: x509.load_pem_x509_certificate,
205*62c56f98SSadaf Ebrahimi                DataFormat.DER: x509.load_der_x509_certificate
206*62c56f98SSadaf Ebrahimi            },
207*62c56f98SSadaf Ebrahimi            DataType.CRL: {
208*62c56f98SSadaf Ebrahimi                DataFormat.PEM: x509.load_pem_x509_crl,
209*62c56f98SSadaf Ebrahimi                DataFormat.DER: x509.load_der_x509_crl
210*62c56f98SSadaf Ebrahimi            },
211*62c56f98SSadaf Ebrahimi            DataType.CSR: {
212*62c56f98SSadaf Ebrahimi                DataFormat.PEM: x509.load_pem_x509_csr,
213*62c56f98SSadaf Ebrahimi                DataFormat.DER: x509.load_der_x509_csr
214*62c56f98SSadaf Ebrahimi            },
215*62c56f98SSadaf Ebrahimi        })
216*62c56f98SSadaf Ebrahimi
217*62c56f98SSadaf Ebrahimi    def collect_default_files(self) -> typing.List[str]:
218*62c56f98SSadaf Ebrahimi        """Collect the default files for parsing."""
219*62c56f98SSadaf Ebrahimi        raise NotImplementedError
220*62c56f98SSadaf Ebrahimi
221*62c56f98SSadaf Ebrahimi    def parse_file(self, filename: str) -> typing.List[AuditData]:
222*62c56f98SSadaf Ebrahimi        """
223*62c56f98SSadaf Ebrahimi        Parse a list of AuditData from file.
224*62c56f98SSadaf Ebrahimi
225*62c56f98SSadaf Ebrahimi        :param filename: name of the file to parse.
226*62c56f98SSadaf Ebrahimi        :return list of AuditData parsed from the file.
227*62c56f98SSadaf Ebrahimi        """
228*62c56f98SSadaf Ebrahimi        raise NotImplementedError
229*62c56f98SSadaf Ebrahimi
230*62c56f98SSadaf Ebrahimi    def parse_bytes(self, data: bytes):
231*62c56f98SSadaf Ebrahimi        """Parse AuditData from bytes."""
232*62c56f98SSadaf Ebrahimi        for data_type in list(DataType):
233*62c56f98SSadaf Ebrahimi            try:
234*62c56f98SSadaf Ebrahimi                result = self.parser[data_type](data)
235*62c56f98SSadaf Ebrahimi            except ValueError as val_error:
236*62c56f98SSadaf Ebrahimi                result = None
237*62c56f98SSadaf Ebrahimi                self.logger.warning(val_error)
238*62c56f98SSadaf Ebrahimi            if result is not None:
239*62c56f98SSadaf Ebrahimi                audit_data = AuditData(data_type, result)
240*62c56f98SSadaf Ebrahimi                return audit_data
241*62c56f98SSadaf Ebrahimi        return None
242*62c56f98SSadaf Ebrahimi
243*62c56f98SSadaf Ebrahimi    def walk_all(self,
244*62c56f98SSadaf Ebrahimi                 results: typing.Dict[str, AuditData],
245*62c56f98SSadaf Ebrahimi                 file_list: typing.Optional[typing.List[str]] = None) \
246*62c56f98SSadaf Ebrahimi        -> None:
247*62c56f98SSadaf Ebrahimi        """
248*62c56f98SSadaf Ebrahimi        Iterate over all the files in the list and get audit data. The
249*62c56f98SSadaf Ebrahimi        results will be written to `results` passed to this function.
250*62c56f98SSadaf Ebrahimi
251*62c56f98SSadaf Ebrahimi        :param results: The dictionary used to store the parsed
252*62c56f98SSadaf Ebrahimi                        AuditData. The keys of this dictionary should
253*62c56f98SSadaf Ebrahimi                        be the identifier of the AuditData.
254*62c56f98SSadaf Ebrahimi        """
255*62c56f98SSadaf Ebrahimi        if file_list is None:
256*62c56f98SSadaf Ebrahimi            file_list = self.default_files
257*62c56f98SSadaf Ebrahimi        for filename in file_list:
258*62c56f98SSadaf Ebrahimi            data_list = self.parse_file(filename)
259*62c56f98SSadaf Ebrahimi            for d in data_list:
260*62c56f98SSadaf Ebrahimi                if d.identifier in results:
261*62c56f98SSadaf Ebrahimi                    results[d.identifier].locations.extend(d.locations)
262*62c56f98SSadaf Ebrahimi                else:
263*62c56f98SSadaf Ebrahimi                    results[d.identifier] = d
264*62c56f98SSadaf Ebrahimi
265*62c56f98SSadaf Ebrahimi    @staticmethod
266*62c56f98SSadaf Ebrahimi    def find_test_dir():
267*62c56f98SSadaf Ebrahimi        """Get the relative path for the MbedTLS test directory."""
268*62c56f98SSadaf Ebrahimi        return os.path.relpath(build_tree.guess_mbedtls_root() + '/tests')
269*62c56f98SSadaf Ebrahimi
270*62c56f98SSadaf Ebrahimi
271*62c56f98SSadaf Ebrahimiclass TestDataAuditor(Auditor):
272*62c56f98SSadaf Ebrahimi    """Class for auditing files in `tests/data_files/`"""
273*62c56f98SSadaf Ebrahimi
274*62c56f98SSadaf Ebrahimi    def collect_default_files(self):
275*62c56f98SSadaf Ebrahimi        """Collect all files in `tests/data_files/`"""
276*62c56f98SSadaf Ebrahimi        test_dir = self.find_test_dir()
277*62c56f98SSadaf Ebrahimi        test_data_glob = os.path.join(test_dir, 'data_files/**')
278*62c56f98SSadaf Ebrahimi        data_files = [f for f in glob.glob(test_data_glob, recursive=True)
279*62c56f98SSadaf Ebrahimi                      if os.path.isfile(f)]
280*62c56f98SSadaf Ebrahimi        return data_files
281*62c56f98SSadaf Ebrahimi
282*62c56f98SSadaf Ebrahimi    def parse_file(self, filename: str) -> typing.List[AuditData]:
283*62c56f98SSadaf Ebrahimi        """
284*62c56f98SSadaf Ebrahimi        Parse a list of AuditData from data file.
285*62c56f98SSadaf Ebrahimi
286*62c56f98SSadaf Ebrahimi        :param filename: name of the file to parse.
287*62c56f98SSadaf Ebrahimi        :return list of AuditData parsed from the file.
288*62c56f98SSadaf Ebrahimi        """
289*62c56f98SSadaf Ebrahimi        with open(filename, 'rb') as f:
290*62c56f98SSadaf Ebrahimi            data = f.read()
291*62c56f98SSadaf Ebrahimi
292*62c56f98SSadaf Ebrahimi        results = []
293*62c56f98SSadaf Ebrahimi        # Try to parse all PEM blocks.
294*62c56f98SSadaf Ebrahimi        is_pem = False
295*62c56f98SSadaf Ebrahimi        for idx, m in enumerate(re.finditer(X509Parser.PEM_REGEX, data, flags=re.S), 1):
296*62c56f98SSadaf Ebrahimi            is_pem = True
297*62c56f98SSadaf Ebrahimi            result = self.parse_bytes(data[m.start():m.end()])
298*62c56f98SSadaf Ebrahimi            if result is not None:
299*62c56f98SSadaf Ebrahimi                result.locations.append("{}#{}".format(filename, idx))
300*62c56f98SSadaf Ebrahimi                results.append(result)
301*62c56f98SSadaf Ebrahimi
302*62c56f98SSadaf Ebrahimi        # Might be DER format.
303*62c56f98SSadaf Ebrahimi        if not is_pem:
304*62c56f98SSadaf Ebrahimi            result = self.parse_bytes(data)
305*62c56f98SSadaf Ebrahimi            if result is not None:
306*62c56f98SSadaf Ebrahimi                result.locations.append("{}".format(filename))
307*62c56f98SSadaf Ebrahimi                results.append(result)
308*62c56f98SSadaf Ebrahimi
309*62c56f98SSadaf Ebrahimi        return results
310*62c56f98SSadaf Ebrahimi
311*62c56f98SSadaf Ebrahimi
312*62c56f98SSadaf Ebrahimidef parse_suite_data(data_f):
313*62c56f98SSadaf Ebrahimi    """
314*62c56f98SSadaf Ebrahimi    Parses .data file for test arguments that possiblly have a
315*62c56f98SSadaf Ebrahimi    valid X.509 data. If you need a more precise parser, please
316*62c56f98SSadaf Ebrahimi    use generate_test_code.parse_test_data instead.
317*62c56f98SSadaf Ebrahimi
318*62c56f98SSadaf Ebrahimi    :param data_f: file object of the data file.
319*62c56f98SSadaf Ebrahimi    :return: Generator that yields test function argument list.
320*62c56f98SSadaf Ebrahimi    """
321*62c56f98SSadaf Ebrahimi    for line in data_f:
322*62c56f98SSadaf Ebrahimi        line = line.strip()
323*62c56f98SSadaf Ebrahimi        # Skip comments
324*62c56f98SSadaf Ebrahimi        if line.startswith('#'):
325*62c56f98SSadaf Ebrahimi            continue
326*62c56f98SSadaf Ebrahimi
327*62c56f98SSadaf Ebrahimi        # Check parameters line
328*62c56f98SSadaf Ebrahimi        match = re.search(r'\A\w+(.*:)?\"', line)
329*62c56f98SSadaf Ebrahimi        if match:
330*62c56f98SSadaf Ebrahimi            # Read test vectors
331*62c56f98SSadaf Ebrahimi            parts = re.split(r'(?<!\\):', line)
332*62c56f98SSadaf Ebrahimi            parts = [x for x in parts if x]
333*62c56f98SSadaf Ebrahimi            args = parts[1:]
334*62c56f98SSadaf Ebrahimi            yield args
335*62c56f98SSadaf Ebrahimi
336*62c56f98SSadaf Ebrahimi
337*62c56f98SSadaf Ebrahimiclass SuiteDataAuditor(Auditor):
338*62c56f98SSadaf Ebrahimi    """Class for auditing files in `tests/suites/*.data`"""
339*62c56f98SSadaf Ebrahimi
340*62c56f98SSadaf Ebrahimi    def collect_default_files(self):
341*62c56f98SSadaf Ebrahimi        """Collect all files in `tests/suites/*.data`"""
342*62c56f98SSadaf Ebrahimi        test_dir = self.find_test_dir()
343*62c56f98SSadaf Ebrahimi        suites_data_folder = os.path.join(test_dir, 'suites')
344*62c56f98SSadaf Ebrahimi        data_files = glob.glob(os.path.join(suites_data_folder, '*.data'))
345*62c56f98SSadaf Ebrahimi        return data_files
346*62c56f98SSadaf Ebrahimi
347*62c56f98SSadaf Ebrahimi    def parse_file(self, filename: str):
348*62c56f98SSadaf Ebrahimi        """
349*62c56f98SSadaf Ebrahimi        Parse a list of AuditData from test suite data file.
350*62c56f98SSadaf Ebrahimi
351*62c56f98SSadaf Ebrahimi        :param filename: name of the file to parse.
352*62c56f98SSadaf Ebrahimi        :return list of AuditData parsed from the file.
353*62c56f98SSadaf Ebrahimi        """
354*62c56f98SSadaf Ebrahimi        audit_data_list = []
355*62c56f98SSadaf Ebrahimi        data_f = FileWrapper(filename)
356*62c56f98SSadaf Ebrahimi        for test_args in parse_suite_data(data_f):
357*62c56f98SSadaf Ebrahimi            for idx, test_arg in enumerate(test_args):
358*62c56f98SSadaf Ebrahimi                match = re.match(r'"(?P<data>[0-9a-fA-F]+)"', test_arg)
359*62c56f98SSadaf Ebrahimi                if not match:
360*62c56f98SSadaf Ebrahimi                    continue
361*62c56f98SSadaf Ebrahimi                if not X509Parser.check_hex_string(match.group('data')):
362*62c56f98SSadaf Ebrahimi                    continue
363*62c56f98SSadaf Ebrahimi                audit_data = self.parse_bytes(bytes.fromhex(match.group('data')))
364*62c56f98SSadaf Ebrahimi                if audit_data is None:
365*62c56f98SSadaf Ebrahimi                    continue
366*62c56f98SSadaf Ebrahimi                audit_data.locations.append("{}:{}:#{}".format(filename,
367*62c56f98SSadaf Ebrahimi                                                               data_f.line_no,
368*62c56f98SSadaf Ebrahimi                                                               idx + 1))
369*62c56f98SSadaf Ebrahimi                audit_data_list.append(audit_data)
370*62c56f98SSadaf Ebrahimi
371*62c56f98SSadaf Ebrahimi        return audit_data_list
372*62c56f98SSadaf Ebrahimi
373*62c56f98SSadaf Ebrahimi
374*62c56f98SSadaf Ebrahimidef list_all(audit_data: AuditData):
375*62c56f98SSadaf Ebrahimi    for loc in audit_data.locations:
376*62c56f98SSadaf Ebrahimi        print("{}\t{:20}\t{:20}\t{:3}\t{}".format(
377*62c56f98SSadaf Ebrahimi            audit_data.identifier,
378*62c56f98SSadaf Ebrahimi            audit_data.not_valid_before.isoformat(timespec='seconds'),
379*62c56f98SSadaf Ebrahimi            audit_data.not_valid_after.isoformat(timespec='seconds'),
380*62c56f98SSadaf Ebrahimi            audit_data.data_type.name,
381*62c56f98SSadaf Ebrahimi            loc))
382*62c56f98SSadaf Ebrahimi
383*62c56f98SSadaf Ebrahimi
384*62c56f98SSadaf Ebrahimidef main():
385*62c56f98SSadaf Ebrahimi    """
386*62c56f98SSadaf Ebrahimi    Perform argument parsing.
387*62c56f98SSadaf Ebrahimi    """
388*62c56f98SSadaf Ebrahimi    parser = argparse.ArgumentParser(description=__doc__)
389*62c56f98SSadaf Ebrahimi
390*62c56f98SSadaf Ebrahimi    parser.add_argument('-a', '--all',
391*62c56f98SSadaf Ebrahimi                        action='store_true',
392*62c56f98SSadaf Ebrahimi                        help='list the information of all the files')
393*62c56f98SSadaf Ebrahimi    parser.add_argument('-v', '--verbose',
394*62c56f98SSadaf Ebrahimi                        action='store_true', dest='verbose',
395*62c56f98SSadaf Ebrahimi                        help='show logs')
396*62c56f98SSadaf Ebrahimi    parser.add_argument('--from', dest='start_date',
397*62c56f98SSadaf Ebrahimi                        help=('Start of desired validity period (UTC, YYYY-MM-DD). '
398*62c56f98SSadaf Ebrahimi                              'Default: today'),
399*62c56f98SSadaf Ebrahimi                        metavar='DATE')
400*62c56f98SSadaf Ebrahimi    parser.add_argument('--to', dest='end_date',
401*62c56f98SSadaf Ebrahimi                        help=('End of desired validity period (UTC, YYYY-MM-DD). '
402*62c56f98SSadaf Ebrahimi                              'Default: --from'),
403*62c56f98SSadaf Ebrahimi                        metavar='DATE')
404*62c56f98SSadaf Ebrahimi    parser.add_argument('--data-files', action='append', nargs='*',
405*62c56f98SSadaf Ebrahimi                        help='data files to audit',
406*62c56f98SSadaf Ebrahimi                        metavar='FILE')
407*62c56f98SSadaf Ebrahimi    parser.add_argument('--suite-data-files', action='append', nargs='*',
408*62c56f98SSadaf Ebrahimi                        help='suite data files to audit',
409*62c56f98SSadaf Ebrahimi                        metavar='FILE')
410*62c56f98SSadaf Ebrahimi
411*62c56f98SSadaf Ebrahimi    args = parser.parse_args()
412*62c56f98SSadaf Ebrahimi
413*62c56f98SSadaf Ebrahimi    # start main routine
414*62c56f98SSadaf Ebrahimi    # setup logger
415*62c56f98SSadaf Ebrahimi    logger = logging.getLogger()
416*62c56f98SSadaf Ebrahimi    logging_util.configure_logger(logger)
417*62c56f98SSadaf Ebrahimi    logger.setLevel(logging.DEBUG if args.verbose else logging.ERROR)
418*62c56f98SSadaf Ebrahimi
419*62c56f98SSadaf Ebrahimi    td_auditor = TestDataAuditor(logger)
420*62c56f98SSadaf Ebrahimi    sd_auditor = SuiteDataAuditor(logger)
421*62c56f98SSadaf Ebrahimi
422*62c56f98SSadaf Ebrahimi    data_files = []
423*62c56f98SSadaf Ebrahimi    suite_data_files = []
424*62c56f98SSadaf Ebrahimi    if args.data_files is None and args.suite_data_files is None:
425*62c56f98SSadaf Ebrahimi        data_files = td_auditor.default_files
426*62c56f98SSadaf Ebrahimi        suite_data_files = sd_auditor.default_files
427*62c56f98SSadaf Ebrahimi    else:
428*62c56f98SSadaf Ebrahimi        if args.data_files is not None:
429*62c56f98SSadaf Ebrahimi            data_files = [x for l in args.data_files for x in l]
430*62c56f98SSadaf Ebrahimi        if args.suite_data_files is not None:
431*62c56f98SSadaf Ebrahimi            suite_data_files = [x for l in args.suite_data_files for x in l]
432*62c56f98SSadaf Ebrahimi
433*62c56f98SSadaf Ebrahimi    # validity period start date
434*62c56f98SSadaf Ebrahimi    if args.start_date:
435*62c56f98SSadaf Ebrahimi        start_date = datetime.datetime.fromisoformat(args.start_date)
436*62c56f98SSadaf Ebrahimi    else:
437*62c56f98SSadaf Ebrahimi        start_date = datetime.datetime.today()
438*62c56f98SSadaf Ebrahimi    # validity period end date
439*62c56f98SSadaf Ebrahimi    if args.end_date:
440*62c56f98SSadaf Ebrahimi        end_date = datetime.datetime.fromisoformat(args.end_date)
441*62c56f98SSadaf Ebrahimi    else:
442*62c56f98SSadaf Ebrahimi        end_date = start_date
443*62c56f98SSadaf Ebrahimi
444*62c56f98SSadaf Ebrahimi    # go through all the files
445*62c56f98SSadaf Ebrahimi    audit_results = {}
446*62c56f98SSadaf Ebrahimi    td_auditor.walk_all(audit_results, data_files)
447*62c56f98SSadaf Ebrahimi    sd_auditor.walk_all(audit_results, suite_data_files)
448*62c56f98SSadaf Ebrahimi
449*62c56f98SSadaf Ebrahimi    logger.info("Total: {} objects found!".format(len(audit_results)))
450*62c56f98SSadaf Ebrahimi
451*62c56f98SSadaf Ebrahimi    # we filter out the files whose validity duration covers the provided
452*62c56f98SSadaf Ebrahimi    # duration.
453*62c56f98SSadaf Ebrahimi    filter_func = lambda d: (start_date < d.not_valid_before) or \
454*62c56f98SSadaf Ebrahimi                            (d.not_valid_after < end_date)
455*62c56f98SSadaf Ebrahimi
456*62c56f98SSadaf Ebrahimi    sortby_end = lambda d: d.not_valid_after
457*62c56f98SSadaf Ebrahimi
458*62c56f98SSadaf Ebrahimi    if args.all:
459*62c56f98SSadaf Ebrahimi        filter_func = None
460*62c56f98SSadaf Ebrahimi
461*62c56f98SSadaf Ebrahimi    # filter and output the results
462*62c56f98SSadaf Ebrahimi    for d in sorted(filter(filter_func, audit_results.values()), key=sortby_end):
463*62c56f98SSadaf Ebrahimi        list_all(d)
464*62c56f98SSadaf Ebrahimi
465*62c56f98SSadaf Ebrahimi    logger.debug("Done!")
466*62c56f98SSadaf Ebrahimi
467*62c56f98SSadaf Ebrahimicheck_cryptography_version()
468*62c56f98SSadaf Ebrahimiif __name__ == "__main__":
469*62c56f98SSadaf Ebrahimi    main()
470