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