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