From a79fef7a09970a316434632e64d7bb57bb1e93ff Mon Sep 17 00:00:00 2001 From: "deepsource-autofix[bot]" <62050782+deepsource-autofix[bot]@users.noreply.github.com> Date: Sun, 17 Jul 2022 22:25:07 +0000 Subject: [PATCH 01/44] Remove unnecessary whitespace --- certificate/certificateModule.py | 8 ++++---- data/calculateStats.py | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/certificate/certificateModule.py b/certificate/certificateModule.py index 1595329..0bffa24 100644 --- a/certificate/certificateModule.py +++ b/certificate/certificateModule.py @@ -53,7 +53,7 @@ def getCertificate(__hostname, __port): except OSError as e: connectHost = __hostname + ":" + str(__port) print(connectHost + ' - OSError - ', e.strerror) - + return __hostnameData @staticmethod @@ -305,12 +305,12 @@ def convertCertificateObject2Json(self, __hostname, __port, __startTime, __endTi myJsonCertificateInfo["startTime"] = startTime myJsonCertificateInfo["endTime"] = endTime myJsonCertificateInfo["queryTime"] = queryTime - + if __certificateObject["connectionCipher"] is not None: myJsonCertificateInfo["connectionCipher"] = __certificateObject["connectionCipher"] myJsonCertificateInfo["certificateInfo"] = {} - + if __certificateObject["certificateMetaData"] is not None: certKeys = __certificateObject.keys() @@ -354,7 +354,7 @@ def convertCertificateObject2Json(self, __hostname, __port, __startTime, __endTi # Certificate template time validity # Work out the time that certificates are issued for myJsonCertificateInfo["certificateTemplateTime"] = self.calculateCertificateTemplateTime(__certificateObject["certificateMetaData"]["notBefore"], __certificateObject["certificateMetaData"]["notAfter"]) - + # Reset number of entries subjectAltNameCounter = 0 diff --git a/data/calculateStats.py b/data/calculateStats.py index 58760e8..786693e 100644 --- a/data/calculateStats.py +++ b/data/calculateStats.py @@ -12,19 +12,19 @@ class calculateStats: """This calculates statistics off the data provided.""" - + @staticmethod def convertTimeIntoHumanReadable(__seconds): """Return the remaining time left on the certificate.""" # Get date/time since epoch based off seconds myDateTime = datetime.datetime.fromtimestamp(__seconds) - + # Create epoch time datetime object. beginDate = datetime.date(1970, 1, 1) - + # Calculate the difference between the 2 dates myDateTime and beginDate myDateTimeObject = relativedelta(myDateTime, beginDate) - + myDateTime = { 'years': myDateTimeObject.years, 'months': myDateTimeObject.months, @@ -33,7 +33,7 @@ def convertTimeIntoHumanReadable(__seconds): 'minutes': myDateTimeObject.minutes, 'seconds': myDateTimeObject.seconds, } - + timeYMDHMS = [] # Iterate through the myDateTime dict and formulate a list of the values. @@ -45,7 +45,7 @@ def convertTimeIntoHumanReadable(__seconds): if myDateTime[field] == 1: timeYMDHMS.append("%d %s" % (myDateTime[field], field[:-1])) myDateTimeString = ', '.join(timeYMDHMS) - + # Return the human readable form string. return myDateTimeString @@ -88,7 +88,7 @@ def calculateStatistics(self, __certResults): def combineData(self, __certResults, __mySystemInfo, __scriptStartTime, __scriptEndTime): """"Combines all the data into structured data.""" - + # Convert script start/end times into string isoformat scriptStartTime = __scriptStartTime.isoformat() scriptEndTime = __scriptEndTime.isoformat() From 9b615c61f39b731ffd3a31d4ab41ae1b5d45b09e Mon Sep 17 00:00:00 2001 From: "deepsource-autofix[bot]" <62050782+deepsource-autofix[bot]@users.noreply.github.com> Date: Sun, 17 Jul 2022 22:27:18 +0000 Subject: [PATCH 02/44] Remove blank lines after docstring --- data/calculateStats.py | 1 - data/sendDataMongoDB.py | 1 - 2 files changed, 2 deletions(-) diff --git a/data/calculateStats.py b/data/calculateStats.py index 786693e..85ec2fc 100644 --- a/data/calculateStats.py +++ b/data/calculateStats.py @@ -88,7 +88,6 @@ def calculateStatistics(self, __certResults): def combineData(self, __certResults, __mySystemInfo, __scriptStartTime, __scriptEndTime): """"Combines all the data into structured data.""" - # Convert script start/end times into string isoformat scriptStartTime = __scriptStartTime.isoformat() scriptEndTime = __scriptEndTime.isoformat() diff --git a/data/sendDataMongoDB.py b/data/sendDataMongoDB.py index 9f00230..bccfaeb 100644 --- a/data/sendDataMongoDB.py +++ b/data/sendDataMongoDB.py @@ -105,7 +105,6 @@ def createDB(self, __destination): def createCollection(self, __mongoConnection, __mongoConfiguration): """create a collection within the DB.""" - # First check to see see if collectionName is defined in mongo.cfg if "collectionName" in __mongoConfiguration: # Retrieve the collectionName value from the dict __mongoConfiguration From 9b67e276f0a2ef2f0d6b6373625ca40101ceb68a Mon Sep 17 00:00:00 2001 From: Git Date: Sun, 17 Jul 2022 15:30:16 -0700 Subject: [PATCH 03/44] Fixing minor docstring syntax --- data/calculateStats.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/calculateStats.py b/data/calculateStats.py index 85ec2fc..4d6db22 100644 --- a/data/calculateStats.py +++ b/data/calculateStats.py @@ -87,7 +87,7 @@ def calculateStatistics(self, __certResults): return combinedStatistics def combineData(self, __certResults, __mySystemInfo, __scriptStartTime, __scriptEndTime): - """"Combines all the data into structured data.""" + """Combines all the data into structured data.""" # Convert script start/end times into string isoformat scriptStartTime = __scriptStartTime.isoformat() scriptEndTime = __scriptEndTime.isoformat() From b759b7891bbd1aba521e0ba955844ffdf293e6ea Mon Sep 17 00:00:00 2001 From: Git Date: Sun, 17 Jul 2022 15:54:55 -0700 Subject: [PATCH 04/44] Fixing issue #3 --- data/sendDataMongoDB.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/data/sendDataMongoDB.py b/data/sendDataMongoDB.py index bccfaeb..2f1d285 100644 --- a/data/sendDataMongoDB.py +++ b/data/sendDataMongoDB.py @@ -1,5 +1,8 @@ -# Send data to destination based on mongo.cfg file. -# Version 0.04 +# Class: sendDataMongoDB +# Last updated: 2022/07/17 +# Author: TheScriptGuy (https://github.com/TheScriptGuy +# Version: 0.06 +# Description: Send json list to mongoDB based on configuration in mongo.cfg import pymongo from pymongo import MongoClient @@ -129,8 +132,8 @@ def uploadDataToMongoDB(self, __jsonScriptData): # Convert the startTime and endTime fields info ISODate format. jsonScriptData = __jsonScriptData - jsonScriptData["scriptStartTime"] = datetime.fromisoformat(jsonScriptData["scriptStartTime"]) - jsonScriptData["scriptEndTime"] = datetime.fromisoformat(jsonScriptData["scriptEndTime"]) + jsonScriptData["queryStatistics"]["scriptStartTime"] = datetime.fromisoformat(jsonScriptData["queryStatistics"]["scriptStartTime"]) + jsonScriptData["queryStatistics"]["scriptEndTime"] = datetime.fromisoformat(jsonScriptData["queryStatistics"]["scriptEndTime"]) for iResult in jsonScriptData["certResults"]: iResult["startTime"] = datetime.fromisoformat(iResult["startTime"]) @@ -145,4 +148,4 @@ def uploadDataToMongoDB(self, __jsonScriptData): def __init__(self): """Initialize the sendDataMongoDB class.""" self.initialized = True - self.version = "0.05" + self.version = "0.06" From 468580c5be72be721e48c4aaae2f601d4c21e682 Mon Sep 17 00:00:00 2001 From: Git Date: Fri, 22 Jul 2022 19:20:25 -0700 Subject: [PATCH 05/44] MongoDB improvements --- CHANGELOG.md | 6 ++++++ data/sendDataMongoDB.py | 36 +++++++++++++++++++++++++----------- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8017520..fcb2a70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# 2022/07/22 +## Version 0.30 +### Changes +* improved MongoDB connection string handling. +* improved MongoDB error handling. + # 2022/07/17 ## Version 0.30 ### Changes diff --git a/data/sendDataMongoDB.py b/data/sendDataMongoDB.py index 2f1d285..883aaaa 100644 --- a/data/sendDataMongoDB.py +++ b/data/sendDataMongoDB.py @@ -1,7 +1,7 @@ # Class: sendDataMongoDB -# Last updated: 2022/07/17 +# Last updated: 2022/07/22 # Author: TheScriptGuy (https://github.com/TheScriptGuy -# Version: 0.06 +# Version: 0.07 # Description: Send json list to mongoDB based on configuration in mongo.cfg import pymongo @@ -26,7 +26,9 @@ def loadConfigurationFile(__fileName="mongo.cfg"): except FileNotFoundError: print(f"Cannot find file {__fileName}.") sys.exit(1) - + except json.decoder.JSONDecodeError: + print(f"Error with mongo.cfg config file - {e}") + sys.exit(1) except Exception as e: print(f"{e} - Error occured.") sys.exit(1) @@ -39,6 +41,9 @@ def sendResults(__results, __destCollection): except pymongo.errors.ServerSelectionTimeoutError: print("Server connection timeout error when uploading data.") sys.exit(1) + except pymongo.errors.OperationFailure as e: + print(f"Mongo operation error - {e}") + sys.exit(1) return __uploadResult @@ -73,16 +78,25 @@ def connectionString(__destination): __mongoLoginCredentials = f"{__mongoUsername}:{__mongoPassword}" # Check to see if __mongoUri is an IP address or not. - if iptools.ipv4.validate_ip(__mongoUri): - __srv = "" + if "cluster" in __destination and __destination["cluster"] == True: + __srv = "+srv" else: - #__srv = "+srv" # automatically enables TLS __srv = "" + + # Check to see if TLS is defined for secure connection. + if "tls" in __destination and __destination["tls"] == True: + __tls = "&tls=true" + + # Get the collection name. If it's empty, use the default. + if "collectionName" in __destination and __destination["collectionName"] != "": + __collectionName = __destination["collectionName"] + else: + __collectionName = "certdataGlobal" if __mongoLoginCredentials == "": - __mongoConnectionString = f"mongodb{__srv}://{__mongoUri}/" + __mongoConnectionString = f"mongodb{__srv}://{__mongoUri}/{__tls}" else: - __mongoConnectionString = f"mongodb{__srv}://{__mongoLoginCredentials}@{__mongoUri}/" + __mongoConnectionString = f"mongodb{__srv}://{__mongoLoginCredentials}@{__mongoUri}/{__collectionName}{__tls}" return __mongoConnectionString def createDB(self, __destination): @@ -97,8 +111,8 @@ def createDB(self, __destination): try: __mongoClient = MongoClient(__mongoConnectionString) - except pymongo.errors.ConfigurationError: - print("mongo.cfg configuration error.") + except pymongo.errors.ConfigurationError as e: + print(f"mongo.cfg configuration error. {e}") sys.exit(1) except pymongo.errors.ServerSelectionTimeoutError: print("Server connection timeout error.") @@ -148,4 +162,4 @@ def uploadDataToMongoDB(self, __jsonScriptData): def __init__(self): """Initialize the sendDataMongoDB class.""" self.initialized = True - self.version = "0.06" + self.version = "0.07" From cc7ed7a160b17b838a1ad9f7940d5c2616be5ab1 Mon Sep 17 00:00:00 2001 From: Git Date: Fri, 22 Jul 2022 19:30:13 -0700 Subject: [PATCH 06/44] MongoDB improvements --- data/sendDataMongoDB.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/data/sendDataMongoDB.py b/data/sendDataMongoDB.py index 883aaaa..23faeae 100644 --- a/data/sendDataMongoDB.py +++ b/data/sendDataMongoDB.py @@ -6,7 +6,6 @@ import pymongo from pymongo import MongoClient -import iptools import json import sys from datetime import datetime @@ -26,7 +25,7 @@ def loadConfigurationFile(__fileName="mongo.cfg"): except FileNotFoundError: print(f"Cannot find file {__fileName}.") sys.exit(1) - except json.decoder.JSONDecodeError: + except json.decoder.JSONDecodeError as e: print(f"Error with mongo.cfg config file - {e}") sys.exit(1) except Exception as e: @@ -78,13 +77,13 @@ def connectionString(__destination): __mongoLoginCredentials = f"{__mongoUsername}:{__mongoPassword}" # Check to see if __mongoUri is an IP address or not. - if "cluster" in __destination and __destination["cluster"] == True: + if "cluster" in __destination and __destination["cluster"] is True: __srv = "+srv" else: __srv = "" - + # Check to see if TLS is defined for secure connection. - if "tls" in __destination and __destination["tls"] == True: + if "tls" in __destination and __destination["tls"] is True: __tls = "&tls=true" # Get the collection name. If it's empty, use the default. From c580ab7f61e499455e1415d70d03997cc045bff6 Mon Sep 17 00:00:00 2001 From: Git Date: Sun, 23 Oct 2022 16:02:08 -0700 Subject: [PATCH 07/44] Version 0.31. See CHANGELOG.md for updates. --- CHANGELOG.md | 56 +++++++ README-json.md | 357 +++++++++++++++++++++++++++++++++++------ README.md | 4 +- certCheck.py | 6 +- data/calculateStats.py | 69 +++++++- 5 files changed, 437 insertions(+), 55 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fcb2a70..7ee2ceb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,59 @@ +# 2022/10/23 +## Version 0.31 +### Changes +* updated dataFormatVersion to 20: + * added new fields - `lowestTemplateTime`, `lowestTemplateTimeHumanReadable`, `highestTemplateTime`, `highestTemplateTimeHumanReadable` + * added Certificate Authority Common Name count field - `commonCAIssuersCount` + * added Common Cipher Information Connection Count - `commonCipherInfoCount` +* Example new json structure (to see more, please see [here](https://github.com/TheScriptGuy/certificateChecker/blob/main/README-json.md)): +```json +"queryStatistics": +{ + "scriptStartTime": "2022-10-23T22:50:12.830321", + "scriptEndTime": "2022-10-23T22:50:14.251425", + "scriptExecutionTime": 1421.1, + "averageQueryTime": 282.82, + "averageCertificateUtilization": 38.05, + "averageTemplateTime": 22584959, + "averageTemplateTimeHumanReadable": "8 months, 18 days, 2 hours, 35 minutes, 59 seconds", + "lowestCertificateTemplateTime": 7775999, + "lowestCertificateTemplateTimeHumanReadable": "2 months, 30 days, 15 hours, 59 minutes, 59 seconds", + "highestCertificateTemplateTime": 34127999, + "highestCertificateTemplateTimeHumanReadable": "1 year, 29 days, 15 hours, 59 minutes, 59 seconds", + "commonCAIssuersCount": + { + "Apple Public EV Server ECC CA 1 - G1": 1, + "Cloudflare Inc ECC CA-3": 1, + "COMODO RSA Organization Validation Secure Server CA": 1, + "R3": 2 + }, + "commonCipherInfoCount": + { + "bits": + { + "128": 2, + "256": 3 + }, + "cipher": + { + "TLS_AES_128_GCM_SHA256": 1, + "TLS_AES_256_GCM_SHA384": 3, + "ECDHE-RSA-AES128-GCM-SHA256": 1 + }, + "version": + { + "TLSv1.3": 4, + "TLSv1.2": 1 + } + }, + "numberofTests": + { + "success": 5, + "failed": 1 + } +} +``` + # 2022/07/22 ## Version 0.30 ### Changes diff --git a/README-json.md b/README-json.md index fcb3634..16a0554 100644 --- a/README-json.md +++ b/README-json.md @@ -56,19 +56,42 @@ The resulting output is: ```json { "tenantId": "", - "deviceId": "2a44a3c9-3051-405f-b7ae-9c070d76c57d", + "deviceId": "2a4123c9-3051-405f-b7ae-9341346c57d", "deviceTag": "", - "clientHostName": "calvin", - "dataFormatVersion": 19, + "clientHostName": "PRODMON01", + "dataFormatVersion": 20, "queryStatistics": { - "scriptStartTime": "2022-07-17T22:12:54.498603", - "scriptEndTime": "2022-07-17T22:12:54.541030", - "scriptExecutionTime": 42.43, - "averageQueryTime": 42.43, - "averageCertificateUtilization": 20.76, + "scriptStartTime": "2022-10-23T22:57:55.637526", + "scriptEndTime": "2022-10-23T22:57:55.684981", + "scriptExecutionTime": 47.45, + "averageQueryTime": 47.45, + "averageCertificateUtilization": 45.58, "averageTemplateTime": 34127999, "averageTemplateTimeHumanReadable": "1 year, 29 days, 15 hours, 59 minutes, 59 seconds", + "lowestCertificateTemplateTime": 34127999, + "lowestCertificateTemplateTimeHumanReadable": "1 year, 29 days, 15 hours, 59 minutes, 59 seconds", + "highestCertificateTemplateTime": 34127999, + "highestCertificateTemplateTimeHumanReadable": "1 year, 29 days, 15 hours, 59 minutes, 59 seconds", + "commonCAIssuersCount": + { + "Apple Public EV Server ECC CA 1 - G1": 1 + }, + "commonCipherInfoCount": + { + "bits": + { + "128": 1 + }, + "cipher": + { + "TLS_AES_128_GCM_SHA256": 1 + }, + "version": + { + "TLSv1.3": 1 + } + }, "numberofTests": { "success": 1, @@ -80,9 +103,9 @@ The resulting output is: { "hostname": "apple.com", "port": 443, - "startTime": "2022-07-17T22:12:54.498603", - "endTime": "2022-07-17T22:12:54.541030", - "queryTime": 42.43, + "startTime": "2022-10-23T22:57:55.637526", + "endTime": "2022-10-23T22:57:55.684981", + "queryTime": 47.45, "connectionCipher": [ "TLS_AES_128_GCM_SHA256", @@ -110,8 +133,8 @@ The resulting output is: "DNS0": "apple.com" } }, - "timeLeft": "10 months, 8 days, 23 hours, 45 minutes, 42 seconds", - "percentageUtilization": 20.76, + "timeLeft": "7 months, 2 days, 23 hours, 41 seconds", + "percentageUtilization": 45.58, "certificateTemplateTime": 34127999 } ] @@ -129,19 +152,49 @@ The resulting output is (take note of the invalid host data at the end of the ce "deviceId": "2a44a3c9-3051-405f-b7ae-9c070d76c57d", "deviceTag": "", "clientHostName": "PRODMON01", - "dataFormatVersion": 19, + "dataFormatVersion": 20, "queryStatistics": { - "scriptStartTime": "2022-07-17T22:07:46.284438", - "scriptEndTime": "2022-07-17T22:07:46.652491", - "scriptExecutionTime": 368.05, - "averageQueryTime": 121.33, - "averageCertificateUtilization": 29.43, - "averageTemplateTime": 24508799, - "averageTemplateTimeHumanReadable": "9 months, 10 days, 8 hours, 59 minutes, 59 seconds", + "scriptStartTime": "2022-10-23T22:50:12.830321", + "scriptEndTime": "2022-10-23T22:50:14.251425", + "scriptExecutionTime": 1421.1, + "averageQueryTime": 282.82, + "averageCertificateUtilization": 38.05, + "averageTemplateTime": 22584959, + "averageTemplateTimeHumanReadable": "8 months, 18 days, 2 hours, 35 minutes, 59 seconds", + "lowestCertificateTemplateTime": 7775999, + "lowestCertificateTemplateTimeHumanReadable": "2 months, 30 days, 15 hours, 59 minutes, 59 seconds", + "highestCertificateTemplateTime": 34127999, + "highestCertificateTemplateTimeHumanReadable": "1 year, 29 days, 15 hours, 59 minutes, 59 seconds", + "commonCAIssuersCount": + { + "Apple Public EV Server ECC CA 1 - G1": 1, + "Cloudflare Inc ECC CA-3": 1, + "COMODO RSA Organization Validation Secure Server CA": 1, + "R3": 2 + }, + "commonCipherInfoCount": + { + "bits": + { + "128": 2, + "256": 3 + }, + "cipher": + { + "TLS_AES_128_GCM_SHA256": 1, + "TLS_AES_256_GCM_SHA384": 3, + "ECDHE-RSA-AES128-GCM-SHA256": 1 + }, + "version": + { + "TLSv1.3": 4, + "TLSv1.2": 1 + } + }, "numberofTests": { - "success": 3, + "success": 5, "failed": 1 } }, @@ -150,9 +203,9 @@ The resulting output is (take note of the invalid host data at the end of the ce { "hostname": "apple.com", "port": 443, - "startTime": "2022-07-17T22:07:46.284476", - "endTime": "2022-07-17T22:07:46.328282", - "queryTime": 43.81, + "startTime": "2022-10-23T22:50:12.830354", + "endTime": "2022-10-23T22:50:12.874737", + "queryTime": 44.38, "connectionCipher": [ "TLS_AES_128_GCM_SHA256", @@ -180,16 +233,16 @@ The resulting output is (take note of the invalid host data at the end of the ce "DNS0": "apple.com" } }, - "timeLeft": "10 months, 8 days, 23 hours, 50 minutes, 50 seconds", - "percentageUtilization": 20.76, + "timeLeft": "7 months, 2 days, 23 hours, 8 minutes, 24 seconds", + "percentageUtilization": 45.58, "certificateTemplateTime": 34127999 }, { "hostname": "news24.com", "port": 443, - "startTime": "2022-07-17T22:07:46.329542", - "endTime": "2022-07-17T22:07:46.373214", - "queryTime": 43.67, + "startTime": "2022-10-23T22:50:12.876082", + "endTime": "2022-10-23T22:50:12.946077", + "queryTime": 70.0, "connectionCipher": [ "TLS_AES_256_GCM_SHA384", @@ -218,20 +271,234 @@ The resulting output is (take note of the invalid host data at the end of the ce "DNS1": "news24.com" } }, - "timeLeft": "9 months, 13 days, 1 hour, 52 minutes, 13 seconds", - "percentageUtilization": 21.56, + "timeLeft": "6 months, 7 days, 1 hour, 9 minutes, 47 seconds", + "percentageUtilization": 48.35, "certificateTemplateTime": 31622399 }, { "hostname": "reuters.com", "port": 443, - "startTime": "2022-07-17T22:07:46.373394", - "endTime": "2022-07-17T22:07:46.649901", - "queryTime": 276.51, + "startTime": "2022-10-23T22:50:12.946226", + "endTime": "2022-10-23T22:50:13.222335", + "queryTime": 276.11, "connectionCipher": [ - "ECDHE-RSA-AES256-GCM-SHA384", + "ECDHE-RSA-AES128-GCM-SHA256", "TLSv1.2", + 128 + ], + "certificateInfo": + { + "certificateIssuer": + { + "countryName": "GB", + "stateOrProvinceName": "Greater Manchester", + "localityName": "Salford", + "organizationName": "COMODO CA Limited", + "commonName": "COMODO RSA Organization Validation Secure Server CA" + }, + "version": 3, + "serialNumber": "E0D89C311AF7835D847BB6EBD4BB4E82", + "notBefore": "Oct 14 00:00:00 2022 GMT", + "notAfter": "Oct 14 23:59:59 2023 GMT", + "caIssuers": + [ + "http://crt.comodoca.com/COMODORSAOrganizationValidationSecureServerCA.crt" + ], + "subjectAltName": + { + "DNS0": "thomsonreuters.com", + "DNS1": "*.casecenter.tr.com", + "DNS2": "*.findlaw.com", + "DNS3": "*.int.thomsonreuters.com", + "DNS4": "*.learnlive.com", + "DNS5": "*.mobile.reuters.com", + "DNS6": "*.reuters.com", + "DNS7": "*.thomson.com", + "DNS8": "*.thomsonreuters.com", + "DNS9": "*.westlaw.com", + "DNS10": "acceluslms.com", + "DNS11": "adviser.accelus.com", + "DNS12": "archbolde-update.co.uk", + "DNS13": "breakingviews.com", + "DNS14": "carswell.com", + "DNS15": "caselines.com", + "DNS16": "clb1.canadalawbook.ca", + "DNS17": "clb7.canadalawbook.ca", + "DNS18": "cpeasy.com", + "DNS19": "cvmailasia.com", + "DNS20": "deskcopy.thomsonreuters.ca", + "DNS21": "editionsyvonblais.com", + "DNS22": "fastsalestax.com", + "DNS23": "find.support.checkpoint.thomsonreuters.com", + "DNS24": "findandprint.com", + "DNS25": "findprint.com", + "DNS26": "funds.in.reuters.com", + "DNS27": "funds.uk.reuters.com", + "DNS28": "funds.us.reuters.com", + "DNS29": "highq.com", + "DNS30": "hk-lawyer.org", + "DNS31": "iblj.com", + "DNS32": "jctadmin.com", + "DNS33": "laley.com.ar", + "DNS34": "lawtel.com", + "DNS35": "legalbusinessonline.com", + "DNS36": "legaledcenter.com", + "DNS37": "livenotecentral.com", + "DNS38": "login.westlawasia.com", + "DNS39": "login.westlawindia.com", + "DNS40": "oconnors.com", + "DNS41": "onesourcelogin.com.au", + "DNS42": "onesourcelogin.eu", + "DNS43": "onesourcetax.com", + "DNS44": "ordermgr.courtexpress.westlaw.com", + "DNS45": "quickview.com", + "DNS46": "reuters.co.uk", + "DNS47": "reuters.com", + "DNS48": "reuters.com.cn", + "DNS49": "reuters.de", + "DNS50": "reuters.es", + "DNS51": "reuters.fr", + "DNS52": "reuters.it", + "DNS53": "reutersagency.com", + "DNS54": "reutersconnect.com", + "DNS55": "roundhall.ie", + "DNS56": "serengetilaw.com", + "DNS57": "services.serengetilaw.com", + "DNS58": "stockscreener.in.reuters.com", + "DNS59": "stockscreener.uk.reuters.com", + "DNS60": "stockscreener.us.reuters.com", + "DNS61": "support.riahome.com", + "DNS62": "support2.riahome.com", + "DNS63": "sweetandmaxwell.co.uk", + "DNS64": "thomson.com", + "DNS65": "thomsonreuters.ca", + "DNS66": "thomsonreuters.cn", + "DNS67": "thomsonreuters.co.jp", + "DNS68": "thomsonreuters.co.kr", + "DNS69": "thomsonreuters.co.nz", + "DNS70": "thomsonreuters.com.au", + "DNS71": "thomsonreuters.com.br", + "DNS72": "thomsonreuters.com.hk", + "DNS73": "thomsonreuters.com.my", + "DNS74": "thomsonreuters.com.pe", + "DNS75": "thomsonreuters.com.sg", + "DNS76": "thomsonreuters.es", + "DNS77": "thomsonreuters.in", + "DNS78": "thomsonreutersmexico.com", + "DNS79": "tr.com", + "DNS80": "tracker.serengetilaw.com", + "DNS81": "training.digita.thomsonreuters.com", + "DNS82": "triform.com", + "DNS83": "westfindandprint.com", + "DNS84": "westfindprint.com", + "DNS85": "westlawasia.com", + "DNS86": "westlawnextcanada.com", + "DNS87": "www.acceluslms.com", + "DNS88": "www.adviser.accelus.com", + "DNS89": "www.adviser.westlaw.com", + "DNS90": "www.analytics.hotprod.westlaw.com", + "DNS91": "www.analytics.qed.westlaw.com", + "DNS92": "www.analytics.westlaw.com", + "DNS93": "www.archbolde-update.co.uk", + "DNS94": "www.ca.practicallaw.thomsonreuters.com", + "DNS95": "www.carswell.com", + "DNS96": "www.cn.reuters.com", + "DNS97": "www.cpeasy.com", + "DNS98": "www.cvmailasia.com", + "DNS99": "www.drafting.westlaw.com", + "DNS100": "www.ediscoverypoint.thomsonreuters.com", + "DNS101": "www.editionsyvonblais.com", + "DNS102": "www.findandprint.com", + "DNS103": "www.findprint.com", + "DNS104": "www.findprint.westlaw.com", + "DNS105": "www.firmcentral.hotprod.westlaw.com", + "DNS106": "www.firmcentral.qed.westlaw.com", + "DNS107": "www.firmcentral.westlaw.com", + "DNS108": "www.forms.hotprod.westlaw.com", + "DNS109": "www.forms.qed.westlaw.com", + "DNS110": "www.forms.westlaw.com", + "DNS111": "www.iblj.com", + "DNS112": "www.laley.com.ar", + "DNS113": "www.lawtel.com", + "DNS114": "www.login.westlawindia.com", + "DNS115": "www.monitorsuite.thomsonreuters.com", + "DNS116": "www.nextcanada.hotprod.westlaw.com", + "DNS117": "www.nextcanada.qed.westlaw.com", + "DNS118": "www.nextcanada.westlaw.com", + "DNS119": "www.oconnors.com", + "DNS120": "www.practicetechnology.thomsonreuters.com", + "DNS121": "www.proview.hotprod.thomsonreuters.com", + "DNS122": "www.proview.thomsonreuters.com", + "DNS123": "www.reuters.co.uk", + "DNS124": "www.reuters.com.cn", + "DNS125": "www.reuters.de", + "DNS126": "www.reuters.es", + "DNS127": "www.reuters.fr", + "DNS128": "www.reuters.it", + "DNS129": "www.roundhall.ie", + "DNS130": "www.serengetilaw.com", + "DNS131": "www.support.riahome.com", + "DNS132": "www.tr.com", + "DNS133": "www.triform.com", + "DNS134": "www.v3.taxnetpro.com", + "DNS135": "www.westfindandprint.com", + "DNS136": "www.westfindprint.com", + "DNS137": "www.westlawnextcanada.com", + "DNS138": "www.wireless.reuters.com" + } + }, + "timeLeft": "11 months, 21 days, 1 hour, 9 minutes, 46 seconds", + "percentageUtilization": 2.72, + "certificateTemplateTime": 31622399 + }, + { + "hostname": "test.remotenode.org", + "port": 443, + "startTime": "2022-10-23T22:50:13.222993", + "endTime": "2022-10-23T22:50:13.608658", + "queryTime": 385.66, + "connectionCipher": + [ + "TLS_AES_256_GCM_SHA384", + "TLSv1.3", + 256 + ], + "certificateInfo": + { + "certificateIssuer": + { + "countryName": "US", + "organizationName": "Let's Encrypt", + "commonName": "R3" + }, + "version": 3, + "serialNumber": "03B092614FDFFC7671BFCC3F1E9B5C014F4A", + "notBefore": "Sep 21 22:12:20 2022 GMT", + "notAfter": "Dec 20 22:12:19 2022 GMT", + "caIssuers": + [ + "http://r3.i.lencr.org/" + ], + "subjectAltName": + { + "DNS0": "test.remotenode.org" + } + }, + "timeLeft": "1 month, 26 days, 23 hours, 22 minutes, 6 seconds", + "percentageUtilization": 35.58, + "certificateTemplateTime": 7775999 + }, + { + "hostname": "mail.remotenode.org", + "port": 465, + "startTime": "2022-10-23T22:50:13.609113", + "endTime": "2022-10-23T22:50:14.247040", + "queryTime": 637.93, + "connectionCipher": + [ + "TLS_AES_256_GCM_SHA384", + "TLSv1.3", 256 ], "certificateInfo": @@ -243,28 +510,28 @@ The resulting output is (take note of the invalid host data at the end of the ce "commonName": "R3" }, "version": 3, - "serialNumber": "04203F2F15F8194772481DABC1061E213EAB", - "notBefore": "Jun 6 12:54:06 2022 GMT", - "notAfter": "Sep 4 12:54:05 2022 GMT", + "serialNumber": "04C41320E31433441C6C91DC18C7F5503D70", + "notBefore": "Sep 1 17:24:09 2022 GMT", + "notAfter": "Nov 30 17:24:08 2022 GMT", "caIssuers": [ "http://r3.i.lencr.org/" ], "subjectAltName": { - "DNS0": "reuters.com" + "DNS0": "mail.remotenode.org" } }, - "timeLeft": "1 month, 17 days, 14 hours, 46 minutes, 19 seconds", - "percentageUtilization": 45.98, + "timeLeft": "1 month, 6 days, 18 hours, 33 minutes, 54 seconds", + "percentageUtilization": 58.03, "certificateTemplateTime": 7775999 }, { "hostname": "thisisaverybadhost.xyz", "port": 443, - "startTime": "2022-07-17T22:07:46.650302", - "endTime": "2022-07-17T22:07:46.652458", - "queryTime": 2.16, + "startTime": "2022-10-23T22:50:14.247608", + "endTime": "2022-10-23T22:50:14.251382", + "queryTime": 3.77, "certificateInfo": { "subject": diff --git a/README.md b/README.md index 5b7d56b..dbd1260 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Certificate Checker -Version: 0.30 +Version: 0.31 Author: TheScriptGuy @@ -29,7 +29,7 @@ python3 certChecker.py --hostname example.com --displayTimeLeft usage: certCheck.py [-h] [--hostname HOSTNAME] [--displayCertificate] [--displayCertificateJSON] [--displayScriptDataJSON] [--displayTimeLeft] [--queryFile QUERYFILE] [--uploadJsonData UPLOADJSONDATA] [--mongoDB] [--sendEmail] [--setTag SETTAG] [--delTag] [--getTag] [--renewDeviceId] [--getDeviceId] [--deleteDeviceId] [--setTenantId SETTENANTID] [--getTenantId] [--delTenantId] [--createBlankConfiguration] -Certificate Checker v0.30 +Certificate Checker v0.31 optional arguments: -h, --help show this help message and exit diff --git a/certCheck.py b/certCheck.py index 7da85d2..5d98ce7 100644 --- a/certCheck.py +++ b/certCheck.py @@ -1,7 +1,7 @@ # Program: Certificate Checker # Author: Nolan Rumble -# Date: 2022/07/17 -# Version: 0.30 +# Date: 2022/10/23 +# Version: 0.31 import argparse import datetime @@ -16,7 +16,7 @@ from data import emailTemplateBuilder from data import sendDataEmail -scriptVersion = "0.30" +scriptVersion = "0.31" # Global Variables args = None diff --git a/data/calculateStats.py b/data/calculateStats.py index 4d6db22..8e52c69 100644 --- a/data/calculateStats.py +++ b/data/calculateStats.py @@ -1,7 +1,7 @@ # Class: calculateStats # Author: Nolan Rumble -# Date: 2022/07/17 -# Version: 0.01 +# Date: 2022/10/23 +# Version: 0.02 import argparse import datetime @@ -55,16 +55,63 @@ def calculateStatistics(self, __certResults): avgUtilization = float(0) avgQueryTime = float(0) avgTemplateTimeSeconds = float(0) + + lowestCertificateTemplateTime = 9999999999999 + highestCertificateTemplateTime = 0 + successfulTests = 0 failedTests = 0 + commonCAIssuers = {} + + commonCipherInfoCount = { + "bits": {}, + "cipher": {}, + "version": {} + } + for item in __certResults: if item["certificateInfo"]["version"] != 0: avgUtilization += item["percentageUtilization"] avgQueryTime += item["queryTime"] avgTemplateTimeSeconds += item["certificateTemplateTime"] + + # Calculate lowest certificate template time. + if lowestCertificateTemplateTime > item["certificateTemplateTime"]: + lowestCertificateTemplateTime = item["certificateTemplateTime"] + + # Calculate highest certificate template time. + if highestCertificateTemplateTime < item["certificateTemplateTime"]: + highestCertificateTemplateTime = item["certificateTemplateTime"] + + caIssuerCommonName = item["certificateInfo"]["certificateIssuer"]["commonName"] + + # Calculate common Certificate Authority Issuers + if caIssuerCommonName in commonCAIssuers: + commonCAIssuers[caIssuerCommonName] += 1 + else: + commonCAIssuers[caIssuerCommonName] = 1 + + # Calculate common cipher connection details + if item["connectionCipher"][0] in commonCipherInfoCount["cipher"]: + commonCipherInfoCount["cipher"][item["connectionCipher"][0]] += 1 + else: + commonCipherInfoCount["cipher"][item["connectionCipher"][0]] = 1 + + if item["connectionCipher"][1] in commonCipherInfoCount["version"]: + commonCipherInfoCount["version"][item["connectionCipher"][1]] += 1 + else: + commonCipherInfoCount["version"][item["connectionCipher"][1]] = 1 + + if item["connectionCipher"][2] in commonCipherInfoCount["bits"]: + commonCipherInfoCount["bits"][item["connectionCipher"][2]] += 1 + else: + commonCipherInfoCount["bits"][item["connectionCipher"][2]] = 1 + + # Increment number of successful tests successfulTests += 1 else: + # Incrememt number of failed tests failedTests += 1 # Round the values to 2 decimal places. @@ -81,7 +128,13 @@ def calculateStatistics(self, __certResults): "averageCertificateUtilization": avgUtilization, "averageQueryTime": avgQueryTime, "averageTemplateTimeSeconds": avgTemplateTimeSeconds, - "averageTemplateTimeHumanReadable": self.convertTimeIntoHumanReadable(avgTemplateTimeSeconds) + "averageTemplateTimeHumanReadable": self.convertTimeIntoHumanReadable(avgTemplateTimeSeconds), + "lowestCertificateTemplateTime": lowestCertificateTemplateTime, + "lowestCertificateTemplateTimeHumanReadable": self.convertTimeIntoHumanReadable(lowestCertificateTemplateTime), + "highestCertificateTemplateTime": highestCertificateTemplateTime, + "highestCertificateTemplateTimeHumanReadable": self.convertTimeIntoHumanReadable(highestCertificateTemplateTime), + "commonCAIssuersCount": commonCAIssuers, + "commonCipherInfoCount": commonCipherInfoCount } return combinedStatistics @@ -111,6 +164,12 @@ def combineData(self, __certResults, __mySystemInfo, __scriptStartTime, __script "averageCertificateUtilization": statistics["averageCertificateUtilization"], "averageTemplateTime": statistics["averageTemplateTimeSeconds"], "averageTemplateTimeHumanReadable": statistics["averageTemplateTimeHumanReadable"], + "lowestCertificateTemplateTime": statistics["lowestCertificateTemplateTime"], + "lowestCertificateTemplateTimeHumanReadable": statistics["lowestCertificateTemplateTimeHumanReadable"], + "highestCertificateTemplateTime": statistics["highestCertificateTemplateTime"], + "highestCertificateTemplateTimeHumanReadable": statistics["highestCertificateTemplateTimeHumanReadable"], + "commonCAIssuersCount": statistics["commonCAIssuersCount"], + "commonCipherInfoCount": statistics["commonCipherInfoCount"], "numberofTests": statistics["numberOfTests"] }, "certResults": __certResults @@ -121,6 +180,6 @@ def combineData(self, __certResults, __mySystemInfo, __scriptStartTime, __script def __init__(self): """Initialize the sendDataMongoDB class.""" self.initialized = True - self.version = "0.01" - self.dataFormatVersion = 19 + self.version = "0.02" + self.dataFormatVersion = 20 From d9bd4cacef69dcb33ed9ea40c3310858a20a7872 Mon Sep 17 00:00:00 2001 From: Git Date: Wed, 26 Oct 2022 17:51:03 -0700 Subject: [PATCH 08/44] Version 0.32 released. Resolve issue #6 --- CHANGELOG.md | 9 +++++++-- README-json.md | 6 +++--- README.md | 4 ++-- certCheck.py | 6 +++--- data/calculateStats.py | 23 +++++++++++++---------- data/sendDataMongoDB.py | 4 ++-- 6 files changed, 30 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ee2ceb..07b2b83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 2022/10/26 +## Version 0.32 +### Fixes +* Resolving [issue](https://github.com/TheScriptGuy/certificateChecker/issues/6) + # 2022/10/23 ## Version 0.31 ### Changes @@ -42,8 +47,8 @@ }, "version": { - "TLSv1.3": 4, - "TLSv1.2": 1 + "TLSv13": 4, + "TLSv12": 1 } }, "numberofTests": diff --git a/README-json.md b/README-json.md index 16a0554..5c4ba05 100644 --- a/README-json.md +++ b/README-json.md @@ -89,7 +89,7 @@ The resulting output is: }, "version": { - "TLSv1.3": 1 + "TLSv13": 1 } }, "numberofTests": @@ -188,8 +188,8 @@ The resulting output is (take note of the invalid host data at the end of the ce }, "version": { - "TLSv1.3": 4, - "TLSv1.2": 1 + "TLSv13": 4, + "TLSv12": 1 } }, "numberofTests": diff --git a/README.md b/README.md index dbd1260..fc6b8a4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Certificate Checker -Version: 0.31 +Version: 0.32 Author: TheScriptGuy @@ -29,7 +29,7 @@ python3 certChecker.py --hostname example.com --displayTimeLeft usage: certCheck.py [-h] [--hostname HOSTNAME] [--displayCertificate] [--displayCertificateJSON] [--displayScriptDataJSON] [--displayTimeLeft] [--queryFile QUERYFILE] [--uploadJsonData UPLOADJSONDATA] [--mongoDB] [--sendEmail] [--setTag SETTAG] [--delTag] [--getTag] [--renewDeviceId] [--getDeviceId] [--deleteDeviceId] [--setTenantId SETTENANTID] [--getTenantId] [--delTenantId] [--createBlankConfiguration] -Certificate Checker v0.31 +Certificate Checker v0.32 optional arguments: -h, --help show this help message and exit diff --git a/certCheck.py b/certCheck.py index 5d98ce7..3145406 100644 --- a/certCheck.py +++ b/certCheck.py @@ -1,7 +1,7 @@ # Program: Certificate Checker # Author: Nolan Rumble -# Date: 2022/10/23 -# Version: 0.31 +# Date: 2022/10/26 +# Version: 0.32 import argparse import datetime @@ -16,7 +16,7 @@ from data import emailTemplateBuilder from data import sendDataEmail -scriptVersion = "0.31" +scriptVersion = "0.32" # Global Variables args = None diff --git a/data/calculateStats.py b/data/calculateStats.py index 8e52c69..a155c4a 100644 --- a/data/calculateStats.py +++ b/data/calculateStats.py @@ -93,20 +93,23 @@ def calculateStatistics(self, __certResults): commonCAIssuers[caIssuerCommonName] = 1 # Calculate common cipher connection details - if item["connectionCipher"][0] in commonCipherInfoCount["cipher"]: - commonCipherInfoCount["cipher"][item["connectionCipher"][0]] += 1 + if str(item["connectionCipher"][0]) in commonCipherInfoCount["cipher"]: + commonCipherInfoCount["cipher"][str(item["connectionCipher"][0])] += 1 else: - commonCipherInfoCount["cipher"][item["connectionCipher"][0]] = 1 + commonCipherInfoCount["cipher"][str(item["connectionCipher"][0])] = 1 + + connectionCipherVersion = str(item["connectionCipher"][1]).replace(".","") - if item["connectionCipher"][1] in commonCipherInfoCount["version"]: - commonCipherInfoCount["version"][item["connectionCipher"][1]] += 1 + if connectionCipherVersion in commonCipherInfoCount["version"]: + commonCipherInfoCount["version"][connectionCipherVersion] += 1 else: - commonCipherInfoCount["version"][item["connectionCipher"][1]] = 1 - - if item["connectionCipher"][2] in commonCipherInfoCount["bits"]: - commonCipherInfoCount["bits"][item["connectionCipher"][2]] += 1 + commonCipherInfoCount["version"][connectionCipherVersion] = 1 + + connectionCipherBits = str(item["connectionCipher"][2]) + if connectionCipherBits in commonCipherInfoCount["bits"]: + commonCipherInfoCount["bits"][connectionCipherBits] += 1 else: - commonCipherInfoCount["bits"][item["connectionCipher"][2]] = 1 + commonCipherInfoCount["bits"][connectionCipherBits] = 1 # Increment number of successful tests successfulTests += 1 diff --git a/data/sendDataMongoDB.py b/data/sendDataMongoDB.py index 23faeae..f477eaf 100644 --- a/data/sendDataMongoDB.py +++ b/data/sendDataMongoDB.py @@ -1,7 +1,7 @@ # Class: sendDataMongoDB -# Last updated: 2022/07/22 +# Last updated: 2022/10/26 # Author: TheScriptGuy (https://github.com/TheScriptGuy -# Version: 0.07 +# Version: 0.08 # Description: Send json list to mongoDB based on configuration in mongo.cfg import pymongo From 20476df62fd44ee416b658e646ec826c206c11b0 Mon Sep 17 00:00:00 2001 From: Git Date: Sun, 20 Nov 2022 14:12:45 -0800 Subject: [PATCH 09/44] Version 0.33. See CHANGELOG.md --- CHANGELOG.md | 4 ++++ README.md | 2 +- certCheck.py | 12 +++++++----- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07b2b83..8a0c30f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,8 @@ # 2022/10/26 +## Version 0.33 +### Changes +* When using --mongoDB argument, the output will include the timestamp of the result being uploaded. + ## Version 0.32 ### Fixes * Resolving [issue](https://github.com/TheScriptGuy/certificateChecker/issues/6) diff --git a/README.md b/README.md index fc6b8a4..160b3ed 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Certificate Checker -Version: 0.32 +Version: 0.33 Author: TheScriptGuy diff --git a/certCheck.py b/certCheck.py index 3145406..de2b788 100644 --- a/certCheck.py +++ b/certCheck.py @@ -1,7 +1,7 @@ # Program: Certificate Checker # Author: Nolan Rumble -# Date: 2022/10/26 -# Version: 0.32 +# Date: 2022/11/20 +# Version: 0.33 import argparse import datetime @@ -16,7 +16,7 @@ from data import emailTemplateBuilder from data import sendDataEmail -scriptVersion = "0.32" +scriptVersion = "0.33" # Global Variables args = None @@ -265,7 +265,8 @@ def processQueryFile(): # Define the sendDataMongoDB object sdMDB = sendDataMongoDB.sendDataMongoDB() uploadResult = sdMDB.uploadDataToMongoDB(myJsonScriptData) - print(uploadResult) + uploadTime = str(datetime.datetime.utcnow()) + print(uploadTime + " - " + str(uploadResult)) if args.sendEmail: # Send an email with the results. @@ -325,7 +326,8 @@ def processHostname(): # Define the sendDataMongoDB object sdMDB = sendDataMongoDB.sendDataMongoDB() uploadResult = sdMDB.uploadDataToMongoDB(jsonScriptData) - print(uploadResult) + uploadTime = str(datetime.datetime.utcnow()) + print(uploadTime + " - " + str(uploadResult)) if args.sendEmail: # Send an email with the results. From b5b7c3929190c4b0b7ed727780ef0270a367f8dd Mon Sep 17 00:00:00 2001 From: Git Date: Sun, 20 Nov 2022 14:51:03 -0800 Subject: [PATCH 10/44] Python coding syntax updates --- data/calculateStats.py | 11 ++++++----- data/emailConfigurationChecker.py | 3 +-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/data/calculateStats.py b/data/calculateStats.py index a155c4a..e0c319f 100644 --- a/data/calculateStats.py +++ b/data/calculateStats.py @@ -10,6 +10,7 @@ from dateutil.relativedelta import relativedelta + class calculateStats: """This calculates statistics off the data provided.""" @@ -75,15 +76,15 @@ def calculateStatistics(self, __certResults): avgUtilization += item["percentageUtilization"] avgQueryTime += item["queryTime"] avgTemplateTimeSeconds += item["certificateTemplateTime"] - + # Calculate lowest certificate template time. if lowestCertificateTemplateTime > item["certificateTemplateTime"]: lowestCertificateTemplateTime = item["certificateTemplateTime"] - + # Calculate highest certificate template time. if highestCertificateTemplateTime < item["certificateTemplateTime"]: highestCertificateTemplateTime = item["certificateTemplateTime"] - + caIssuerCommonName = item["certificateInfo"]["certificateIssuer"]["commonName"] # Calculate common Certificate Authority Issuers @@ -97,8 +98,8 @@ def calculateStatistics(self, __certResults): commonCipherInfoCount["cipher"][str(item["connectionCipher"][0])] += 1 else: commonCipherInfoCount["cipher"][str(item["connectionCipher"][0])] = 1 - - connectionCipherVersion = str(item["connectionCipher"][1]).replace(".","") + + connectionCipherVersion = str(item["connectionCipher"][1]).replace(".", "") if connectionCipherVersion in commonCipherInfoCount["version"]: commonCipherInfoCount["version"][connectionCipherVersion] += 1 diff --git a/data/emailConfigurationChecker.py b/data/emailConfigurationChecker.py index 0f4fbb2..e5a1419 100644 --- a/data/emailConfigurationChecker.py +++ b/data/emailConfigurationChecker.py @@ -145,8 +145,7 @@ def validateConfiguration(self): """Validate configuration file. If all checks pass successfully, return the json object.""" if self.checkConfigurationValid(self.mailConfig): return self.mailConfig - else: - return None + return None def __init__(self, __mailConfigurationFile="mail.cfg"): """Initialize the email configuration checker class.""" From 07364d25b6f86201598ce5dae70a1a7027ce3a46 Mon Sep 17 00:00:00 2001 From: Git Date: Sun, 20 Nov 2022 15:13:28 -0800 Subject: [PATCH 11/44] Updating python syntax --- certificate/certificateModule.py | 10 +++++----- data/calculateStats.py | 12 +++++++----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/certificate/certificateModule.py b/certificate/certificateModule.py index 0bffa24..5657204 100644 --- a/certificate/certificateModule.py +++ b/certificate/certificateModule.py @@ -1,6 +1,6 @@ # Certificate Module1 -# Version: 0.11 -# Last updated: 2022-07-16 +# Version: 0.12 +# Last updated: 2022-11-20 # Author: TheScriptGuy import ssl @@ -130,10 +130,10 @@ def howMuchTimeLeft(self, __certificateObject): for field in myDeltaDate: if myDeltaDate[field] > 1: - timeLeft.append("%d %s" % (myDeltaDate[field], field)) + timeLeft.append(f"{myDeltaDate[field]} {field}") else: if myDeltaDate[field] == 1: - timeLeft.append("%d %s" % (myDeltaDate[field], field[:-1])) + timeLeft.append(f"{myDeltaDate[field]} {field[:-1]}") certResult = ', '.join(timeLeft) else: @@ -390,7 +390,7 @@ def uploadJsonData(self, __certificateJsonData, __httpUrl): def __init__(self): """Initialize the class.""" self.initialized = True - self.moduleVersion = "0.11" + self.moduleVersion = "0.12" self.certificate = {} # Certificate date/time format that is to be interpreted by datetime module. diff --git a/data/calculateStats.py b/data/calculateStats.py index e0c319f..c86c890 100644 --- a/data/calculateStats.py +++ b/data/calculateStats.py @@ -1,7 +1,7 @@ # Class: calculateStats # Author: Nolan Rumble -# Date: 2022/10/23 -# Version: 0.02 +# Date: 2022/11/20 +# Version: 0.03 import argparse import datetime @@ -41,10 +41,12 @@ def convertTimeIntoHumanReadable(__seconds): # If the delimeter field is 0, don't include it in final result for field in myDateTime: if myDateTime[field] > 1: - timeYMDHMS.append("%d %s" % (myDateTime[field], field)) + humanReadable = f"{myDateTime[field]} {field}" + timeYMDHMS.append(humanReadable) else: if myDateTime[field] == 1: - timeYMDHMS.append("%d %s" % (myDateTime[field], field[:-1])) + humanReadable = f"{myDateTime[field]} {field[:-1]}" + timeYMDHMS.append(humanReadable) myDateTimeString = ', '.join(timeYMDHMS) # Return the human readable form string. @@ -184,6 +186,6 @@ def combineData(self, __certResults, __mySystemInfo, __scriptStartTime, __script def __init__(self): """Initialize the sendDataMongoDB class.""" self.initialized = True - self.version = "0.02" + self.version = "0.03" self.dataFormatVersion = 20 From 1657d2512480f3d224cd2a03185ff565f016effe Mon Sep 17 00:00:00 2001 From: Git Date: Sun, 4 Dec 2022 15:24:47 -0800 Subject: [PATCH 12/44] Version 0.34. See CHANGELOG.md --- CHANGELOG.md | 10 +++++++++ README.md | 13 +++++++---- certCheck.py | 37 +++++++++++++++++++++++++------ data/calculateStats.py | 50 ++++++++++++++++++++++++++++-------------- 4 files changed, 83 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a0c30f..c390325 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +# 2022/12/04 +## Version 0.34 +### Additions +* Added the ability to retry attempts if a connection fails. + * `--retryAttempts x`, where x is the number of attempts. Defaults to 1 attempt. + * `--timeBetweenRetries y` where y is the number of seconds between attempts. Defaults to 1 second. + +### Fixes +* Fixed an error when calculating statistics for a single host that didn't exist. + # 2022/10/26 ## Version 0.33 ### Changes diff --git a/README.md b/README.md index 160b3ed..ee98cca 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Certificate Checker -Version: 0.33 +Version: 0.34 Author: TheScriptGuy @@ -26,10 +26,11 @@ python3 certChecker.py --hostname example.com --displayTimeLeft ## Help output ```bash -usage: certCheck.py [-h] [--hostname HOSTNAME] [--displayCertificate] [--displayCertificateJSON] [--displayScriptDataJSON] [--displayTimeLeft] [--queryFile QUERYFILE] [--uploadJsonData UPLOADJSONDATA] [--mongoDB] [--sendEmail] [--setTag SETTAG] - [--delTag] [--getTag] [--renewDeviceId] [--getDeviceId] [--deleteDeviceId] [--setTenantId SETTENANTID] [--getTenantId] [--delTenantId] [--createBlankConfiguration] +usage: certCheck.py [-h] [--hostname HOSTNAME] [--displayCertificate] [--displayCertificateJSON] [--displayScriptDataJSON] [--displayTimeLeft] [--queryFile QUERYFILE] [--uploadJsonData UPLOADJSONDATA] [--mongoDB] + [--sendEmail] [--retryAmount RETRYAMOUNT] [--timeBetweenRetries TIMEBETWEENRETRIES] [--setTag SETTAG] [--delTag] [--getTag] [--renewDeviceId] [--getDeviceId] [--deleteDeviceId] + [--setTenantId SETTENANTID] [--getTenantId] [--delTenantId] [--createBlankConfiguration] -Certificate Checker v0.32 +Certificate Checker v0.34 optional arguments: -h, --help show this help message and exit @@ -46,6 +47,10 @@ optional arguments: Upload JSON data to HTTP URL via HTTP POST method. --mongoDB Upload results to MongoDB. Connection details stored in mongo.cfg --sendEmail Send an email with the results. SMTP connection details stored in mail.cfg + --retryAmount RETRYAMOUNT + Attempt to retry the connection if any error occured. Defaults to 1 attempt. + --timeBetweenRetries TIMEBETWEENRETRIES + The number of seconds between each retry attempt if the connection fails. Defaults to 1 second. --setTag SETTAG Set the tag for the query results. Use commas to separate multiple tags. --delTag Removes the tags from the configuration file. --getTag Get tags from the configuration file. diff --git a/certCheck.py b/certCheck.py index de2b788..c0cd626 100644 --- a/certCheck.py +++ b/certCheck.py @@ -1,12 +1,13 @@ # Program: Certificate Checker # Author: Nolan Rumble -# Date: 2022/11/20 -# Version: 0.33 +# Date: 2022/12/04 +# Version: 0.34 import argparse import datetime import sys import json +import time from systemInfo import systemInfo from certificate import certificateModule @@ -16,7 +17,7 @@ from data import emailTemplateBuilder from data import sendDataEmail -scriptVersion = "0.33" +scriptVersion = "0.34" # Global Variables args = None @@ -58,6 +59,12 @@ def parseArguments(): parser.add_argument('--sendEmail', action='store_true', help='Send an email with the results. SMTP connection details stored in mail.cfg') + parser.add_argument('--retryAmount', default=1, + help='Attempt to retry the connection if any error occured. Defaults to 1 attempt.') + + parser.add_argument('--timeBetweenRetries', default=1, + help='The number of seconds between each retry attempt if the connection fails. Defaults to 1 second.') + parser.add_argument('--setTag', default='', help='Set the tag for the query results. Use commas to separate multiple tags.') @@ -235,8 +242,17 @@ def processQueryFile(): # For SSL performance measurement - START o_startTime = datetime.datetime.utcnow() - # Connect to the hostname from the queryFile argument and get the certificate associated with it. - myCertificate = o_myCertificate.getCertificate(myHostname["hostname"], myHostname["port"]) + # Iterate through number of retryAmount + for counter in range(int(args.retryAmount)): + # Connect to the hostname from the queryFile argument and get the certificate associated with it. + myCertificate = o_myCertificate.getCertificate(myHostname["hostname"], myHostname["port"]) + + if myCertificate["certificateMetaData"] is not None: + break + else: + # If unable to connect to host for whatever reason, pause for a second then try again. + time.sleep(int(args.timeBetweenRetries)) + # For SSL performance measurement - END o_endTime = datetime.datetime.utcnow() @@ -289,14 +305,21 @@ def processHostname(): else: hostnameQuery = {"hostname": args.hostname, "port": 443} - myCertificate = o_myCertificate.getCertificate(hostnameQuery["hostname"], hostnameQuery["port"]) + # Iterate through number of retryAmount + for counter in range(int(args.retryAmount)): + # Connect to the hostname from the queryFile argument and get the certificate associated with it. + myCertificate = o_myCertificate.getCertificate(hostnameQuery["hostname"], hostnameQuery["port"]) + if myCertificate["certificateMetaData"] is not None: + break + else: + # If unable to connect to host for whatever reason, pause for a second then try again. + time.sleep(int(args.timeBetweenRetries)) # For SSL performance measurement - END o_endTime = datetime.datetime.utcnow() # Convert the certificate object into JSON format. jsonCertificateInfo = o_myCertificate.convertCertificateObject2Json(hostnameQuery["hostname"], hostnameQuery["port"], o_startTime, o_endTime, myCertificate) - # Append system data to JSON certificate structure jsonScriptData = gatherData([jsonCertificateInfo], o_startTime, o_endTime) diff --git a/data/calculateStats.py b/data/calculateStats.py index c86c890..3ee910c 100644 --- a/data/calculateStats.py +++ b/data/calculateStats.py @@ -73,6 +73,22 @@ def calculateStatistics(self, __certResults): "version": {} } + combinedStatistics = { + "numberOfTests": { + "success": 0, + "failed": 0 + }, + "averageCertificateUtilization": 0.0, + "averageQueryTime": 0.0, + "averageTemplateTimeSeconds": 0, + "averageTemplateTimeHumanReadable": 0, + "lowestCertificateTemplateTime": 0, + "lowestCertificateTemplateTimeHumanReadable": "0 seconds", + "highestCertificateTemplateTime": 0, + "highestCertificateTemplateTimeHumanReadable": "0 seconds", + "commonCAIssuersCount": commonCAIssuers, + "commonCipherInfoCount": commonCipherInfoCount + } for item in __certResults: if item["certificateInfo"]["version"] != 0: avgUtilization += item["percentageUtilization"] @@ -126,22 +142,24 @@ def calculateStatistics(self, __certResults): avgQueryTime = round(avgQueryTime / successfulTests, 2) avgTemplateTimeSeconds = int(round(avgTemplateTimeSeconds / successfulTests, 2)) - combinedStatistics = { - "numberOfTests": { - "success": successfulTests, - "failed": failedTests - }, - "averageCertificateUtilization": avgUtilization, - "averageQueryTime": avgQueryTime, - "averageTemplateTimeSeconds": avgTemplateTimeSeconds, - "averageTemplateTimeHumanReadable": self.convertTimeIntoHumanReadable(avgTemplateTimeSeconds), - "lowestCertificateTemplateTime": lowestCertificateTemplateTime, - "lowestCertificateTemplateTimeHumanReadable": self.convertTimeIntoHumanReadable(lowestCertificateTemplateTime), - "highestCertificateTemplateTime": highestCertificateTemplateTime, - "highestCertificateTemplateTimeHumanReadable": self.convertTimeIntoHumanReadable(highestCertificateTemplateTime), - "commonCAIssuersCount": commonCAIssuers, - "commonCipherInfoCount": commonCipherInfoCount - } + combinedStatistics = { + "numberOfTests": { + "success": successfulTests, + "failed": failedTests + }, + "averageCertificateUtilization": avgUtilization, + "averageQueryTime": avgQueryTime, + "averageTemplateTimeSeconds": avgTemplateTimeSeconds, + "averageTemplateTimeHumanReadable": self.convertTimeIntoHumanReadable(avgTemplateTimeSeconds), + "lowestCertificateTemplateTime": lowestCertificateTemplateTime, + "lowestCertificateTemplateTimeHumanReadable": self.convertTimeIntoHumanReadable(lowestCertificateTemplateTime), + "highestCertificateTemplateTime": highestCertificateTemplateTime, + "highestCertificateTemplateTimeHumanReadable": self.convertTimeIntoHumanReadable(highestCertificateTemplateTime), + "commonCAIssuersCount": commonCAIssuers, + "commonCipherInfoCount": commonCipherInfoCount + } + else: + combinedStatistics["numberOfTests"]["failed"] = failedTests return combinedStatistics From 000dba9d6d034b227faef86d4078d651d9a89b30 Mon Sep 17 00:00:00 2001 From: Git Date: Sun, 4 Dec 2022 15:34:09 -0800 Subject: [PATCH 13/44] Fixing syntax. --- certCheck.py | 1 - data/calculateStats.py | 7 +++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/certCheck.py b/certCheck.py index c0cd626..ef9504e 100644 --- a/certCheck.py +++ b/certCheck.py @@ -252,7 +252,6 @@ def processQueryFile(): else: # If unable to connect to host for whatever reason, pause for a second then try again. time.sleep(int(args.timeBetweenRetries)) - # For SSL performance measurement - END o_endTime = datetime.datetime.utcnow() diff --git a/data/calculateStats.py b/data/calculateStats.py index 3ee910c..5ebcd70 100644 --- a/data/calculateStats.py +++ b/data/calculateStats.py @@ -1,7 +1,7 @@ # Class: calculateStats # Author: Nolan Rumble -# Date: 2022/11/20 -# Version: 0.03 +# Date: 2022/12/04 +# Version: 0.04 import argparse import datetime @@ -204,6 +204,5 @@ def combineData(self, __certResults, __mySystemInfo, __scriptStartTime, __script def __init__(self): """Initialize the sendDataMongoDB class.""" self.initialized = True - self.version = "0.03" + self.version = "0.04" self.dataFormatVersion = 20 - From 3308b3b8c32e43536ce17b6741868ca165213f95 Mon Sep 17 00:00:00 2001 From: Git Date: Sun, 18 Dec 2022 15:17:24 -0800 Subject: [PATCH 14/44] Improving code syntax. --- data/calculateStats.py | 11 ++++------- data/emailConfigurationChecker.py | 7 +++---- data/sendDataEmail.py | 6 ++---- systemInfo/systemInfo.py | 10 ++++------ 4 files changed, 13 insertions(+), 21 deletions(-) diff --git a/data/calculateStats.py b/data/calculateStats.py index 5ebcd70..eb89dc6 100644 --- a/data/calculateStats.py +++ b/data/calculateStats.py @@ -1,12 +1,9 @@ # Class: calculateStats # Author: Nolan Rumble -# Date: 2022/12/04 -# Version: 0.04 +# Date: 2022/12/18 +# Version: 0.05 -import argparse import datetime -import sys -import time from dateutil.relativedelta import relativedelta @@ -202,7 +199,7 @@ def combineData(self, __certResults, __mySystemInfo, __scriptStartTime, __script return myData def __init__(self): - """Initialize the sendDataMongoDB class.""" + """Initialize the calculateStats class.""" self.initialized = True - self.version = "0.04" + self.version = "0.05" self.dataFormatVersion = 20 diff --git a/data/emailConfigurationChecker.py b/data/emailConfigurationChecker.py index e5a1419..5c8ce87 100644 --- a/data/emailConfigurationChecker.py +++ b/data/emailConfigurationChecker.py @@ -17,10 +17,9 @@ class emailConfigurationChecker: def checkConfigHostname(self, __mailConfigJson): """Check to see if the hostname field is defined""" # Check to see if hostname is defined. This is a mandatory field. - if "hostname" in __mailConfigJson: - if __mailConfigJson["hostname"] == "": - print(f"hostname field is a mandatory field and must be defined in {self.mailConfigurationFile}.") - sys.exit(1) + if "hostname" in __mailConfigJson and __mailConfigJson["hostname"] == "": + print(f"hostname field is a mandatory field and must be defined in {self.mailConfigurationFile}.") + sys.exit(1) def checkConfigPort(self, __mailConfigJson): """Check what the port is configured as. If not defined, assume port 25.""" diff --git a/data/sendDataEmail.py b/data/sendDataEmail.py index e1fd2a4..0f2e3b0 100644 --- a/data/sendDataEmail.py +++ b/data/sendDataEmail.py @@ -1,11 +1,9 @@ # Send email class -# Version: 0.03 -# Last modified: 2022-06-16 +# Version: 0.04 +# Last modified: 2022-12-18 import smtplib import ssl -import sys -from os import path from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart import datetime diff --git a/systemInfo/systemInfo.py b/systemInfo/systemInfo.py index b7234b9..370acae 100644 --- a/systemInfo/systemInfo.py +++ b/systemInfo/systemInfo.py @@ -89,9 +89,8 @@ def checkMyTenantId(__myConfigJson): Returns False if it is not """ result = False - if "myTenantId" in __myConfigJson: - if __myConfigJson["myTenantId"] != "": - return True + if "myTenantId" in __myConfigJson and __myConfigJson["myTenantId"] != "": + result = True return result @staticmethod @@ -112,9 +111,8 @@ def checkMyDeviceId(__myConfigJson): Returns False if is not. """ result = False - if "myDeviceId" in __myConfigJson: - if __myConfigJson["myDeviceId"] != "": - result = True + if "myDeviceId" in __myConfigJson and __myConfigJson["myDeviceId"] != "": + result = True return result def checkIfTenantIdExists(self, __myConfigJson): From 6ab0089341ca628bc21af16ad6b5350f6bb18eb0 Mon Sep 17 00:00:00 2001 From: Git Date: Sun, 18 Dec 2022 15:27:48 -0800 Subject: [PATCH 15/44] Improving code syntax. --- certCheck.py | 2 +- certificate/certificateModule.py | 8 ++++++-- data/calculateStats.py | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/certCheck.py b/certCheck.py index ef9504e..b5a0bdc 100644 --- a/certCheck.py +++ b/certCheck.py @@ -251,7 +251,7 @@ def processQueryFile(): break else: # If unable to connect to host for whatever reason, pause for a second then try again. - time.sleep(int(args.timeBetweenRetries)) + time.sleep(int(args.timeBetweenRetries)) # For SSL performance measurement - END o_endTime = datetime.datetime.utcnow() diff --git a/certificate/certificateModule.py b/certificate/certificateModule.py index 5657204..52d2ac0 100644 --- a/certificate/certificateModule.py +++ b/certificate/certificateModule.py @@ -348,12 +348,16 @@ def convertCertificateObject2Json(self, __hostname, __port, __startTime, __endTi # Time left on certificate myJsonCertificateInfo["timeLeft"] = self.howMuchTimeLeft(__certificateObject) + # Get the notBefore and notAfter dates. + certBeforeDate = __certificateObject["certificateMetaData"]["notBefore"] + certAfterDate = __certificateObject["certificateMetaData"]["notAfter"] + # Percentage Utilization of certificate - myJsonCertificateInfo["percentageUtilization"] = self.calculateCertificateUtilization(__certificateObject["certificateMetaData"]["notBefore"], __certificateObject["certificateMetaData"]["notAfter"]) + myJsonCertificateInfo["percentageUtilization"] = self.calculateCertificateUtilization(certBeforeDate, certAfterDate) # Certificate template time validity # Work out the time that certificates are issued for - myJsonCertificateInfo["certificateTemplateTime"] = self.calculateCertificateTemplateTime(__certificateObject["certificateMetaData"]["notBefore"], __certificateObject["certificateMetaData"]["notAfter"]) + myJsonCertificateInfo["certificateTemplateTime"] = self.calculateCertificateTemplateTime(certBeforeDate, certAfterDate) # Reset number of entries subjectAltNameCounter = 0 diff --git a/data/calculateStats.py b/data/calculateStats.py index eb89dc6..5cfd7e4 100644 --- a/data/calculateStats.py +++ b/data/calculateStats.py @@ -154,7 +154,7 @@ def calculateStatistics(self, __certResults): "highestCertificateTemplateTimeHumanReadable": self.convertTimeIntoHumanReadable(highestCertificateTemplateTime), "commonCAIssuersCount": commonCAIssuers, "commonCipherInfoCount": commonCipherInfoCount - } + } else: combinedStatistics["numberOfTests"]["failed"] = failedTests From 9e36649ac144089716a08c8cd13db1f4993480b5 Mon Sep 17 00:00:00 2001 From: Git Date: Sun, 18 Dec 2022 15:31:05 -0800 Subject: [PATCH 16/44] Improving code syntax. --- certCheck.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/certCheck.py b/certCheck.py index b5a0bdc..51a32b4 100644 --- a/certCheck.py +++ b/certCheck.py @@ -368,7 +368,7 @@ def processHostname(): # initialize jsonCertificateInfo jsonCertificateInfo = {} - jsonScriptData = {} + jsonScriptData = [] if args.queryFile: processQueryFile() From 98d5550c8271c794e2ff124f3a057c127e980d89 Mon Sep 17 00:00:00 2001 From: Git Date: Tue, 3 Jan 2023 07:38:19 -0800 Subject: [PATCH 17/44] Adding LICENSE.md --- LICENSE.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 LICENSE.md diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..202ccc3 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,10 @@ +# LICENSE AGREEMENT + +Free for personal/non-commercial use. + +By downloading this code: +* You are responsible for validating what you run in your environment (whether it be on personal/work/other device, or in a home/corporate/public/other network) +* All liabilities, responsibilities, and actions fall onto whomever/whatever runs this code. +* No warranty provided. + +Custom license agreements available on request. From 4ed0b17f5bc86f9f9faa9573702080762ccca8f3 Mon Sep 17 00:00:00 2001 From: Git Date: Tue, 3 Jan 2023 07:39:20 -0800 Subject: [PATCH 18/44] Adding LICENSE.md --- LICENSE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE.md b/LICENSE.md index 202ccc3..f4b05e9 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -7,4 +7,4 @@ By downloading this code: * All liabilities, responsibilities, and actions fall onto whomever/whatever runs this code. * No warranty provided. -Custom license agreements available on request. +Custom software license agreements available on request. From 0e44abd3e5501c8fc34a305171ce202f483f9074 Mon Sep 17 00:00:00 2001 From: Git Date: Sun, 5 Feb 2023 18:09:42 -0800 Subject: [PATCH 19/44] Version 0.35. See CHANGELOG.md --- CHANGELOG.md | 5 +++++ README.md | 7 ++++--- certCheck.py | 25 +++++++++++++++++++----- certificate/certificateModule.py | 33 ++++++++++++++++++++++++++------ 4 files changed, 56 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c390325..cc7f655 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 2023/02/05 +## Version 0.35 +### Feature additions +* Added the ability to load the context flags from contextVariables.json file. + # 2022/12/04 ## Version 0.34 ### Additions diff --git a/README.md b/README.md index ee98cca..acba457 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Certificate Checker -Version: 0.34 +Version: 0.35 Author: TheScriptGuy @@ -27,10 +27,10 @@ python3 certChecker.py --hostname example.com --displayTimeLeft ## Help output ```bash usage: certCheck.py [-h] [--hostname HOSTNAME] [--displayCertificate] [--displayCertificateJSON] [--displayScriptDataJSON] [--displayTimeLeft] [--queryFile QUERYFILE] [--uploadJsonData UPLOADJSONDATA] [--mongoDB] - [--sendEmail] [--retryAmount RETRYAMOUNT] [--timeBetweenRetries TIMEBETWEENRETRIES] [--setTag SETTAG] [--delTag] [--getTag] [--renewDeviceId] [--getDeviceId] [--deleteDeviceId] + [--sendEmail] [--retryAmount RETRYAMOUNT] [--timeBetweenRetries TIMEBETWEENRETRIES] [--contextVariables] [--setTag SETTAG] [--delTag] [--getTag] [--renewDeviceId] [--getDeviceId] [--deleteDeviceId] [--setTenantId SETTENANTID] [--getTenantId] [--delTenantId] [--createBlankConfiguration] -Certificate Checker v0.34 +Certificate Checker v0.35 optional arguments: -h, --help show this help message and exit @@ -51,6 +51,7 @@ optional arguments: Attempt to retry the connection if any error occured. Defaults to 1 attempt. --timeBetweenRetries TIMEBETWEENRETRIES The number of seconds between each retry attempt if the connection fails. Defaults to 1 second. + --contextVariables Read the context variables from contextVariables.json --setTag SETTAG Set the tag for the query results. Use commas to separate multiple tags. --delTag Removes the tags from the configuration file. --getTag Get tags from the configuration file. diff --git a/certCheck.py b/certCheck.py index 51a32b4..a6a81ff 100644 --- a/certCheck.py +++ b/certCheck.py @@ -1,7 +1,7 @@ # Program: Certificate Checker # Author: Nolan Rumble -# Date: 2022/12/04 -# Version: 0.34 +# Date: 2023/02/05 +# Version: 0.35 import argparse import datetime @@ -17,7 +17,7 @@ from data import emailTemplateBuilder from data import sendDataEmail -scriptVersion = "0.34" +scriptVersion = "0.35" # Global Variables args = None @@ -65,6 +65,9 @@ def parseArguments(): parser.add_argument('--timeBetweenRetries', default=1, help='The number of seconds between each retry attempt if the connection fails. Defaults to 1 second.') + parser.add_argument('--contextVariables', action='store_true', + help='Read the context variables from contextVariables.json') + parser.add_argument('--setTag', default='', help='Set the tag for the query results. Use commas to separate multiple tags.') @@ -236,8 +239,14 @@ def processQueryFile(): scriptStartTime = datetime.datetime.utcnow() for myHostname in myCertData.loadQueriesFile(args.queryFile): + + contextVariables = 0 + + if args.contextVariables: + contextVariables = 1 + # Define initial certificate object - o_myCertificate = certificateModule.certificateModule() + o_myCertificate = certificateModule.certificateModule(contextVariables) # For SSL performance measurement - START o_startTime = datetime.datetime.utcnow() @@ -291,7 +300,12 @@ def processQueryFile(): def processHostname(): """This will attempt to connect to the hostname defined by the --hostname argument.""" # Define initial certificate object - o_myCertificate = certificateModule.certificateModule() + + if args.contextVariables: + contextVariables = 1 + o_myCertificate = certificateModule.certificateModule(contextVariables) + else: + o_myCertificate = certificateModule.certificateModule() # For SSL performance measurement - START o_startTime = datetime.datetime.utcnow() @@ -308,6 +322,7 @@ def processHostname(): for counter in range(int(args.retryAmount)): # Connect to the hostname from the queryFile argument and get the certificate associated with it. myCertificate = o_myCertificate.getCertificate(hostnameQuery["hostname"], hostnameQuery["port"]) + if myCertificate["certificateMetaData"] is not None: break else: diff --git a/certificate/certificateModule.py b/certificate/certificateModule.py index 52d2ac0..84e9177 100644 --- a/certificate/certificateModule.py +++ b/certificate/certificateModule.py @@ -1,6 +1,6 @@ # Certificate Module1 -# Version: 0.12 -# Last updated: 2022-11-20 +# Version: 0.13 +# Last updated: 2023-02-05 # Author: TheScriptGuy import ssl @@ -15,10 +15,29 @@ class certificateModule: """certificateModule class""" - @staticmethod - def getCertificate(__hostname, __port): + def getContextVariables(self): + """Get the variables from the contextVariables.json""" + try: + # Assume contextVariables is empty. + contextVariables = None + + # Attempt to load the contextVariables.json file. + with open('contextVariables.json') as fContextVariables: + contextVariables = json.load(fContextVariables) + + return contextVariables + except FileNotFoundError: + print('I could not find contextVariables.json') + + + def getCertificate(self, __hostname, __port): """Connect to the host and get the certificate.""" __ctx = ssl.create_default_context() + + if self.contextVariables is not None: + if self.contextVariables["securityLevel"] == 1: + # Lower the default security level + __ctx.set_ciphers('DEFAULT@SECLEVEL=1') # Initialize the __hostnameData object. __hostnameData = { @@ -391,11 +410,13 @@ def uploadJsonData(self, __certificateJsonData, __httpUrl): x = requests.post(__httpUrl, json=__certificateJsonData) return x.headers - def __init__(self): + def __init__(self, __contextVariables=0): """Initialize the class.""" self.initialized = True - self.moduleVersion = "0.12" + self.moduleVersion = "0.13" self.certificate = {} + if __contextVariables == 1: + self.contextVariables = self.getContextVariables() # Certificate date/time format that is to be interpreted by datetime module. self.certTimeFormat = "%b %d %H:%M:%S %Y %Z" From c5aa789339226f30957335da639e98dc31054771 Mon Sep 17 00:00:00 2001 From: Git Date: Sat, 25 Mar 2023 22:38:48 -0700 Subject: [PATCH 20/44] Version 0.36. See CHANGELOG.md --- CHANGELOG.md | 5 ++ README.md | 2 +- certCheck.py | 11 ++-- certificate/certificateModule.py | 2 + data/sendDataMongoDB.py | 92 ++++++++++++++++++++++++++++---- 5 files changed, 96 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc7f655..cb1fc9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 2023/03/25 +## Version 0.36 +### Feature additions +* [MongoDB enhancement](https://github.com/TheScriptGuy/certificateChecker/issues/9) + # 2023/02/05 ## Version 0.35 ### Feature additions diff --git a/README.md b/README.md index acba457..dbef927 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Certificate Checker -Version: 0.35 +Version: 0.36 Author: TheScriptGuy diff --git a/certCheck.py b/certCheck.py index a6a81ff..7c56eeb 100644 --- a/certCheck.py +++ b/certCheck.py @@ -1,7 +1,7 @@ # Program: Certificate Checker # Author: Nolan Rumble -# Date: 2023/02/05 -# Version: 0.35 +# Date: 2023/03/25 +# Version: 0.36 import argparse import datetime @@ -17,7 +17,7 @@ from data import emailTemplateBuilder from data import sendDataEmail -scriptVersion = "0.35" +scriptVersion = "0.36" # Global Variables args = None @@ -303,10 +303,11 @@ def processHostname(): if args.contextVariables: contextVariables = 1 - o_myCertificate = certificateModule.certificateModule(contextVariables) else: - o_myCertificate = certificateModule.certificateModule() + contextVariables = 0 + o_myCertificate = certificateModule.certificateModule(contextVariables) + # For SSL performance measurement - START o_startTime = datetime.datetime.utcnow() diff --git a/certificate/certificateModule.py b/certificate/certificateModule.py index 84e9177..cc4e031 100644 --- a/certificate/certificateModule.py +++ b/certificate/certificateModule.py @@ -417,6 +417,8 @@ def __init__(self, __contextVariables=0): self.certificate = {} if __contextVariables == 1: self.contextVariables = self.getContextVariables() + else: + self.contextVariables = None # Certificate date/time format that is to be interpreted by datetime module. self.certTimeFormat = "%b %d %H:%M:%S %Y %Z" diff --git a/data/sendDataMongoDB.py b/data/sendDataMongoDB.py index f477eaf..eef2dae 100644 --- a/data/sendDataMongoDB.py +++ b/data/sendDataMongoDB.py @@ -1,14 +1,16 @@ # Class: sendDataMongoDB -# Last updated: 2022/10/26 +# Last updated: 2023/03/25 # Author: TheScriptGuy (https://github.com/TheScriptGuy -# Version: 0.08 +# Version: 0.09 # Description: Send json list to mongoDB based on configuration in mongo.cfg import pymongo from pymongo import MongoClient import json import sys +import os from datetime import datetime +from bson.objectid import ObjectId class sendDataMongoDB: @@ -33,18 +35,89 @@ def loadConfigurationFile(__fileName="mongo.cfg"): sys.exit(1) @staticmethod - def sendResults(__results, __destCollection): + def sendJsonScriptDataToFile(__fileName, __jsonScriptData): + """Send script data to __filename.""" + with open(__fileName, "a") as fileJsonScriptData: + while len(__jsonScriptData) > 0: + jsonScriptDataItem = __jsonScriptData.pop(0) + jsonScriptDataItem["queryStatistics"]["scriptStartTime"] = jsonScriptDataItem["queryStatistics"]["scriptStartTime"].strftime('%Y-%m-%dT%H:%M:%S.%f') + jsonScriptDataItem["queryStatistics"]["scriptEndTime"] = jsonScriptDataItem["queryStatistics"]["scriptEndTime"].strftime('%Y-%m-%dT%H:%M:%S.%f') + + for iResult in jsonScriptDataItem["certResults"]: + iResult["startTime"] = iResult["startTime"].strftime('%Y-%m-%dT%H:%M:%S.%f') + iResult["endTime"] = iResult["endTime"].strftime('%Y-%m-%dT%H:%M:%S.%f') + + if "_id" in jsonScriptDataItem: + jsonScriptDataItem["_id"] = str(jsonScriptDataItem["_id"]) + + fileJsonScriptData.write(json.dumps(jsonScriptDataItem) + "\n") + + @staticmethod + def getJsonScriptDataFromFile(__fileName): + """Load the contents of __filename into a list.""" + jsonLinesFile = [] + # Check to see if there's a certificateData.json file. If yes, upload its data first. + try: + with open("certificateData.json", "r") as fileJsonScriptData: + while True: + fileLine = fileJsonScriptData.readline().replace("\n","") + if not fileLine: + break + + jsonLine = json.loads(fileLine) + jsonLine["queryStatistics"]["scriptStartTime"] = datetime.fromisoformat(jsonLine["queryStatistics"]["scriptStartTime"]) + jsonLine["queryStatistics"]["scriptEndTime"] = datetime.fromisoformat(jsonLine["queryStatistics"]["scriptEndTime"]) + + for iResult in jsonLine["certResults"]: + iResult["startTime"] = datetime.fromisoformat(iResult["startTime"]) + iResult["endTime"] = datetime.fromisoformat(iResult["endTime"]) + + if "_id" in jsonLine: + jsonLine["_id"] = ObjectId(jsonLine["_id"]) + + jsonLinesFile.append(jsonLine) + + except FileNotFoundError: + # File not found error - just ignore. + pass + + return jsonLinesFile + + def sendResults(self, __results, __destCollection): """upload the __results to __destCollection mongodb object.""" try: - __uploadResult = __destCollection.insert_one(__results) + # First check to see if we need to attempt to upload previous data that was not uploaded. + previousJsonScriptData = self.getJsonScriptDataFromFile("certificateData.json") + previousUploadResult = [] + __uploadResult = [] + + while len(previousJsonScriptData) > 0: + jsonScriptDataItem = previousJsonScriptData.pop(0) + previousUploadResultItem = __destCollection.insert_one(jsonScriptDataItem) + previousUploadResult.append(previousUploadResultItem) + + if os.path.isfile("certificateData.json"): + os.remove("certificateData.json") + + if len(previousJsonScriptData) > 0: + # Didn't finish uploading all the data. Save it to file. + self.sendJsonScriptDataToFile("certificateData.json", previousJsonScriptData) + previousJsonScriptData = [] + + __mongoResult = __destCollection.insert_one(__results) + __uploadResult.append(__mongoResult) except pymongo.errors.ServerSelectionTimeoutError: print("Server connection timeout error when uploading data.") + print("Saving to certificateData.json") + self.sendJsonScriptDataToFile("certificateData.json", [__results]) sys.exit(1) except pymongo.errors.OperationFailure as e: print(f"Mongo operation error - {e}") + print("Saving to certificateData.json") + self.sendJsonScriptDataToFile("certificateData.json", [__results]) sys.exit(1) - return __uploadResult + return previousUploadResult + __uploadResult @staticmethod def connectionString(__destination): @@ -85,6 +158,8 @@ def connectionString(__destination): # Check to see if TLS is defined for secure connection. if "tls" in __destination and __destination["tls"] is True: __tls = "&tls=true" + else: + __tls = "" # Get the collection name. If it's empty, use the default. if "collectionName" in __destination and __destination["collectionName"] != "": @@ -144,7 +219,6 @@ def uploadDataToMongoDB(self, __jsonScriptData): # Convert the startTime and endTime fields info ISODate format. jsonScriptData = __jsonScriptData - jsonScriptData["queryStatistics"]["scriptStartTime"] = datetime.fromisoformat(jsonScriptData["queryStatistics"]["scriptStartTime"]) jsonScriptData["queryStatistics"]["scriptEndTime"] = datetime.fromisoformat(jsonScriptData["queryStatistics"]["scriptEndTime"]) @@ -152,13 +226,11 @@ def uploadDataToMongoDB(self, __jsonScriptData): iResult["startTime"] = datetime.fromisoformat(iResult["startTime"]) iResult["endTime"] = datetime.fromisoformat(iResult["endTime"]) - # Upload the results to the MongoDB - # It's only at this point that the database/collection gets created - # (if this is the first entry to be uploaded) uploadResult = self.sendResults(__jsonScriptData, collection) + return uploadResult def __init__(self): """Initialize the sendDataMongoDB class.""" self.initialized = True - self.version = "0.07" + self.version = "0.09" From d002a588ee0b1a0c147ed64529a70593a9d8e68d Mon Sep 17 00:00:00 2001 From: "deepsource-autofix[bot]" <62050782+deepsource-autofix[bot]@users.noreply.github.com> Date: Sun, 26 Mar 2023 05:46:42 +0000 Subject: [PATCH 21/44] refactor: Remove unnecessary whitespace Blank lines should not contain any tabs or spaces. --- certCheck.py | 10 +++++----- certificate/certificateModule.py | 6 +++--- data/sendDataMongoDB.py | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/certCheck.py b/certCheck.py index 7c56eeb..fc936d9 100644 --- a/certCheck.py +++ b/certCheck.py @@ -239,9 +239,9 @@ def processQueryFile(): scriptStartTime = datetime.datetime.utcnow() for myHostname in myCertData.loadQueriesFile(args.queryFile): - + contextVariables = 0 - + if args.contextVariables: contextVariables = 1 @@ -300,14 +300,14 @@ def processQueryFile(): def processHostname(): """This will attempt to connect to the hostname defined by the --hostname argument.""" # Define initial certificate object - + if args.contextVariables: contextVariables = 1 else: contextVariables = 0 o_myCertificate = certificateModule.certificateModule(contextVariables) - + # For SSL performance measurement - START o_startTime = datetime.datetime.utcnow() @@ -323,7 +323,7 @@ def processHostname(): for counter in range(int(args.retryAmount)): # Connect to the hostname from the queryFile argument and get the certificate associated with it. myCertificate = o_myCertificate.getCertificate(hostnameQuery["hostname"], hostnameQuery["port"]) - + if myCertificate["certificateMetaData"] is not None: break else: diff --git a/certificate/certificateModule.py b/certificate/certificateModule.py index cc4e031..23599e4 100644 --- a/certificate/certificateModule.py +++ b/certificate/certificateModule.py @@ -24,16 +24,16 @@ def getContextVariables(self): # Attempt to load the contextVariables.json file. with open('contextVariables.json') as fContextVariables: contextVariables = json.load(fContextVariables) - + return contextVariables except FileNotFoundError: print('I could not find contextVariables.json') - + def getCertificate(self, __hostname, __port): """Connect to the host and get the certificate.""" __ctx = ssl.create_default_context() - + if self.contextVariables is not None: if self.contextVariables["securityLevel"] == 1: # Lower the default security level diff --git a/data/sendDataMongoDB.py b/data/sendDataMongoDB.py index eef2dae..361ff0e 100644 --- a/data/sendDataMongoDB.py +++ b/data/sendDataMongoDB.py @@ -46,7 +46,7 @@ def sendJsonScriptDataToFile(__fileName, __jsonScriptData): for iResult in jsonScriptDataItem["certResults"]: iResult["startTime"] = iResult["startTime"].strftime('%Y-%m-%dT%H:%M:%S.%f') iResult["endTime"] = iResult["endTime"].strftime('%Y-%m-%dT%H:%M:%S.%f') - + if "_id" in jsonScriptDataItem: jsonScriptDataItem["_id"] = str(jsonScriptDataItem["_id"]) @@ -74,7 +74,7 @@ def getJsonScriptDataFromFile(__fileName): if "_id" in jsonLine: jsonLine["_id"] = ObjectId(jsonLine["_id"]) - + jsonLinesFile.append(jsonLine) except FileNotFoundError: @@ -95,7 +95,7 @@ def sendResults(self, __results, __destCollection): jsonScriptDataItem = previousJsonScriptData.pop(0) previousUploadResultItem = __destCollection.insert_one(jsonScriptDataItem) previousUploadResult.append(previousUploadResultItem) - + if os.path.isfile("certificateData.json"): os.remove("certificateData.json") @@ -103,7 +103,7 @@ def sendResults(self, __results, __destCollection): # Didn't finish uploading all the data. Save it to file. self.sendJsonScriptDataToFile("certificateData.json", previousJsonScriptData) previousJsonScriptData = [] - + __mongoResult = __destCollection.insert_one(__results) __uploadResult.append(__mongoResult) except pymongo.errors.ServerSelectionTimeoutError: From 5002a4918b3d63dfdad79af350e43f1962b25bf2 Mon Sep 17 00:00:00 2001 From: Git Date: Sat, 15 Apr 2023 12:21:18 -0700 Subject: [PATCH 22/44] Version 0.37. See CHANGELOG.md --- CHANGELOG.md | 5 +++++ README.md | 4 ++-- certCheck.py | 6 +++--- data/sendDataMongoDB.py | 23 +++++++++++++++-------- 4 files changed, 25 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb1fc9b..5684e19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 2023/04/15 +## Version 0.37 +### Feature additions +* [Adding time to output when error occurs](https://github.com/TheScriptGuy/certificateChecker/issues/11) + # 2023/03/25 ## Version 0.36 ### Feature additions diff --git a/README.md b/README.md index dbef927..7679ee5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Certificate Checker -Version: 0.36 +Version: 0.37 Author: TheScriptGuy @@ -30,7 +30,7 @@ usage: certCheck.py [-h] [--hostname HOSTNAME] [--displayCertificate] [--display [--sendEmail] [--retryAmount RETRYAMOUNT] [--timeBetweenRetries TIMEBETWEENRETRIES] [--contextVariables] [--setTag SETTAG] [--delTag] [--getTag] [--renewDeviceId] [--getDeviceId] [--deleteDeviceId] [--setTenantId SETTENANTID] [--getTenantId] [--delTenantId] [--createBlankConfiguration] -Certificate Checker v0.35 +Certificate Checker v0.37 optional arguments: -h, --help show this help message and exit diff --git a/certCheck.py b/certCheck.py index fc936d9..161de7c 100644 --- a/certCheck.py +++ b/certCheck.py @@ -1,7 +1,7 @@ # Program: Certificate Checker # Author: Nolan Rumble -# Date: 2023/03/25 -# Version: 0.36 +# Date: 2023/04/15 +# Version: 0.37 import argparse import datetime @@ -17,7 +17,7 @@ from data import emailTemplateBuilder from data import sendDataEmail -scriptVersion = "0.36" +scriptVersion = "0.37" # Global Variables args = None diff --git a/data/sendDataMongoDB.py b/data/sendDataMongoDB.py index 361ff0e..943c624 100644 --- a/data/sendDataMongoDB.py +++ b/data/sendDataMongoDB.py @@ -1,7 +1,7 @@ # Class: sendDataMongoDB -# Last updated: 2023/03/25 -# Author: TheScriptGuy (https://github.com/TheScriptGuy -# Version: 0.09 +# Last updated: 2023/04/15 +# Author: TheScriptGuy (https://github.com/TheScriptGuy) +# Version: 0.10 # Description: Send json list to mongoDB based on configuration in mongo.cfg import pymongo @@ -107,13 +107,20 @@ def sendResults(self, __results, __destCollection): __mongoResult = __destCollection.insert_one(__results) __uploadResult.append(__mongoResult) except pymongo.errors.ServerSelectionTimeoutError: - print("Server connection timeout error when uploading data.") - print("Saving to certificateData.json") + # Get time of error + errTime = str(datetime.datetime.utcnow()) + print(f"{errTime} - Server connection timeout error when uploading data. Saving to certificateData.json") + + # Save test data to file. self.sendJsonScriptDataToFile("certificateData.json", [__results]) sys.exit(1) + except pymongo.errors.OperationFailure as e: - print(f"Mongo operation error - {e}") - print("Saving to certificateData.json") + # Get time of error + errTime = str(datetime.datetime.utcnow()) + print(f"{errTime} - Mongo operation error - {e}. Saving to certificateData.json") + + # Save test data to file. self.sendJsonScriptDataToFile("certificateData.json", [__results]) sys.exit(1) @@ -233,4 +240,4 @@ def uploadDataToMongoDB(self, __jsonScriptData): def __init__(self): """Initialize the sendDataMongoDB class.""" self.initialized = True - self.version = "0.09" + self.version = "0.10" From cb4b99808296c48be22cbffbfbd4930313d74d55 Mon Sep 17 00:00:00 2001 From: Git Date: Sat, 15 Apr 2023 15:14:27 -0700 Subject: [PATCH 23/44] Version 0.38. See CHANGELOG.md --- CHANGELOG.md | 4 ++++ README.md | 25 +++++++++++++++++++++---- certCheck.py | 35 ++++++++++++++++++++++++++--------- systemInfo/systemInfo.py | 19 +++++++++++++------ 4 files changed, 64 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5684e19..6b3ea8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,8 @@ # 2023/04/15 +## Version 0.38 +### Feature additions +* [Allow environment variables usage](https://github.com/TheScriptGuy/certificateChecker/issues/13) + ## Version 0.37 ### Feature additions * [Adding time to output when error occurs](https://github.com/TheScriptGuy/certificateChecker/issues/11) diff --git a/README.md b/README.md index 7679ee5..f90b5a3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Certificate Checker -Version: 0.37 +Version: 0.38 Author: TheScriptGuy @@ -27,10 +27,10 @@ python3 certChecker.py --hostname example.com --displayTimeLeft ## Help output ```bash usage: certCheck.py [-h] [--hostname HOSTNAME] [--displayCertificate] [--displayCertificateJSON] [--displayScriptDataJSON] [--displayTimeLeft] [--queryFile QUERYFILE] [--uploadJsonData UPLOADJSONDATA] [--mongoDB] - [--sendEmail] [--retryAmount RETRYAMOUNT] [--timeBetweenRetries TIMEBETWEENRETRIES] [--contextVariables] [--setTag SETTAG] [--delTag] [--getTag] [--renewDeviceId] [--getDeviceId] [--deleteDeviceId] - [--setTenantId SETTENANTID] [--getTenantId] [--delTenantId] [--createBlankConfiguration] + [--sendEmail] [--retryAmount RETRYAMOUNT] [--timeBetweenRetries TIMEBETWEENRETRIES] [--contextVariables] [--environmentVariables] [--setTag SETTAG] [--delTag] [--getTag] [--renewDeviceId] + [--getDeviceId] [--deleteDeviceId] [--setTenantId SETTENANTID] [--getTenantId] [--delTenantId] [--createBlankConfiguration] -Certificate Checker v0.37 +Certificate Checker v0.38 optional arguments: -h, --help show this help message and exit @@ -52,6 +52,8 @@ optional arguments: --timeBetweenRetries TIMEBETWEENRETRIES The number of seconds between each retry attempt if the connection fails. Defaults to 1 second. --contextVariables Read the context variables from contextVariables.json + --environmentVariables + Uses the environment values for TENANT_ID and TAG to set the runtime environment. --setTag SETTAG Set the tag for the query results. Use commas to separate multiple tags. --delTag Removes the tags from the configuration file. --getTag Get tags from the configuration file. @@ -66,6 +68,21 @@ optional arguments: Creates a blank configuration file template - myConfig.json. Overwrites any existing configuration ``` +## Environment variables +The script will by default attempt to look at a configuration file for the Tenant ID and Tags. If the environment variables are defined, then it will use that in preference to the configuration file. + +To use, first define the variables: +```bash +$ export TENANT_ID="" +$ export TAGS="" +``` + +Now that environment variables are defined, we can use the `--environmentVariables` argument: +```bash +$ python3 certCheck.py --hostname apple.com --environmentVariables --displayScriptDataJSON +``` + + [Example to send an email](https://github.com/TheScriptGuy/certificateChecker/blob/main/README-email.md) [Example to upload to mongoDB](https://github.com/TheScriptGuy/certificateChecker/blob/main/README-mongoDB.md) diff --git a/certCheck.py b/certCheck.py index 161de7c..bd1a88e 100644 --- a/certCheck.py +++ b/certCheck.py @@ -1,13 +1,14 @@ # Program: Certificate Checker # Author: Nolan Rumble # Date: 2023/04/15 -# Version: 0.37 +# Version: 0.38 import argparse import datetime import sys import json import time +import os from systemInfo import systemInfo from certificate import certificateModule @@ -17,7 +18,7 @@ from data import emailTemplateBuilder from data import sendDataEmail -scriptVersion = "0.37" +scriptVersion = "0.38" # Global Variables args = None @@ -68,6 +69,9 @@ def parseArguments(): parser.add_argument('--contextVariables', action='store_true', help='Read the context variables from contextVariables.json') + parser.add_argument('--environmentVariables', action='store_true', + help='Uses the environment values for TENANT_ID and TAG to set the runtime environment.') + parser.add_argument('--setTag', default='', help='Set the tag for the query results. Use commas to separate multiple tags.') @@ -110,9 +114,21 @@ def defineInfoArguments(o_systemInfo): o_systemInfo.createBlankConfigurationFile() sys.exit(0) + # Check to see if the environmentVariables argument is set. + if args.environmentVariables: + tenantId = os.environ.get('TENANT_ID') + tags = os.environ.get('TAGS') + if tenantId and tags: + o_systemInfo.setTag(tags, False) + o_systemInfo.setTenantId(tenantId, False) + else: + print('Environment variables TENANT_ID and TAGS are not both set.') + sys.exit(1) + + # If setTag argument is set, create the new Tag. if args.setTag: - o_systemInfo.setTag(args.setTag) + o_systemInfo.setTag(args.setTag, True) print('New tag set.') sys.exit(0) @@ -144,7 +160,7 @@ def defineInfoArguments(o_systemInfo): # If setTenantId is set, add it to the configuration file. if args.setTenantId: - o_systemInfo.setTenantId(args.setTenantId) + o_systemInfo.setTenantId(args.setTenantId, True) sys.exit(0) # If getTenantId is set, retrieve the tenant Id from the configuration file. @@ -158,7 +174,7 @@ def defineInfoArguments(o_systemInfo): sys.exit(0) -def gatherData(__certResults, __scriptStartTime, __scriptEndTime): +def gatherData(__certResults, __mySystemInfo, __scriptStartTime, __scriptEndTime): """ This will collect all the data into a uniform data structure that can help with measuring results across multiple executions. @@ -177,11 +193,11 @@ def gatherData(__certResults, __scriptStartTime, __scriptEndTime): * averageTemplateTime - Average template time being used across all the tests. * queryResults - The results of all queries that were performed against the nameservers. """ - mySystemInfo = systemInfo.systemInfo() + myDetails = calculateStats.calculateStats() # Create the json script structure with all the meta data. - myData = myDetails.combineData(__certResults, mySystemInfo, __scriptStartTime, __scriptEndTime) + myData = myDetails.combineData(__certResults, __mySystemInfo, __scriptStartTime, __scriptEndTime) return myData @@ -274,7 +290,7 @@ def processQueryFile(): scriptEndTime = datetime.datetime.utcnow() - myJsonScriptData = gatherData(jsonScriptData, scriptStartTime, scriptEndTime) + myJsonScriptData = gatherData(jsonScriptData, o_myInfo, scriptStartTime, scriptEndTime) if args.displayScriptDataJSON: # Display the certificate and system JSON structure @@ -335,8 +351,9 @@ def processHostname(): # Convert the certificate object into JSON format. jsonCertificateInfo = o_myCertificate.convertCertificateObject2Json(hostnameQuery["hostname"], hostnameQuery["port"], o_startTime, o_endTime, myCertificate) + # Append system data to JSON certificate structure - jsonScriptData = gatherData([jsonCertificateInfo], o_startTime, o_endTime) + jsonScriptData = gatherData([jsonCertificateInfo], o_myInfo, o_startTime, o_endTime) if args.displayCertificateJSON: # Display the certificate JSON structure diff --git a/systemInfo/systemInfo.py b/systemInfo/systemInfo.py index 370acae..9cebf13 100644 --- a/systemInfo/systemInfo.py +++ b/systemInfo/systemInfo.py @@ -1,4 +1,7 @@ +# Version: 0.05 +# Date: 2023/04/15 # systemInfo class + import socket from os import path import json @@ -44,7 +47,7 @@ def getTag(self): # Return the value of __myTags return __myTags - def setTag(self, __tagName): + def setTag(self, __tagName, __writeConfig): """Set the tag for data aggregation purposes.""" # Separate out all the tags using commas __newTagName = __tagName.rstrip().split(',') @@ -55,8 +58,9 @@ def setTag(self, __tagName): # Update the class's myConfigJson variable. self.myConfigJson["myTags"] = __newTagName - # Update the configuration file. - self.updateMyConfig() + # If __writeConfig is true, then write the configuration file. + if __writeConfig: + self.updateMyConfig() def deleteTag(self): """Delete the tag.""" @@ -72,10 +76,13 @@ def getTenantId(self): """Returns the tenant Id.""" return self.myConfigJson["myTenantId"] - def setTenantId(self, __myTenantId): + def setTenantId(self, __myTenantId, __writeConfig): """Sets the tenant Id for the script.""" self.myConfigJson["myTenantId"] = __myTenantId - self.updateMyConfig() + + # If __writeConfig is true, then write it to file. + if __writeConfig: + self.updateMyConfig() def deleteTenantId(self): """Deletes the tenant Id and updates configuration file.""" @@ -167,7 +174,7 @@ def refreshConfigFile(self): def __init__(self, __myConfigFile="myConfig.json"): """Initialize the class.""" # Define the class version. - self.classVersion = "0.04" + self.classVersion = "0.05" self.myConfigJson = {} From 461bb9d9d98e5716d0b6af0a342d432b42fa0f86 Mon Sep 17 00:00:00 2001 From: Git Date: Sat, 15 Apr 2023 15:25:06 -0700 Subject: [PATCH 24/44] Version 0.38. See CHANGELOG.d --- certCheck.py | 1 - systemInfo/systemInfo.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/certCheck.py b/certCheck.py index bd1a88e..fa30407 100644 --- a/certCheck.py +++ b/certCheck.py @@ -193,7 +193,6 @@ def gatherData(__certResults, __mySystemInfo, __scriptStartTime, __scriptEndTime * averageTemplateTime - Average template time being used across all the tests. * queryResults - The results of all queries that were performed against the nameservers. """ - myDetails = calculateStats.calculateStats() # Create the json script structure with all the meta data. diff --git a/systemInfo/systemInfo.py b/systemInfo/systemInfo.py index 9cebf13..48c68b4 100644 --- a/systemInfo/systemInfo.py +++ b/systemInfo/systemInfo.py @@ -79,14 +79,14 @@ def getTenantId(self): def setTenantId(self, __myTenantId, __writeConfig): """Sets the tenant Id for the script.""" self.myConfigJson["myTenantId"] = __myTenantId - + # If __writeConfig is true, then write it to file. if __writeConfig: self.updateMyConfig() def deleteTenantId(self): """Deletes the tenant Id and updates configuration file.""" - self.setTenantId("") + self.setTenantId("", True) @staticmethod def checkMyTenantId(__myConfigJson): From 66c77fde4cd2fe48a899e8a6de53cdfcfc2bbec4 Mon Sep 17 00:00:00 2001 From: Git Date: Sat, 15 Apr 2023 15:34:26 -0700 Subject: [PATCH 25/44] Fixing syntax --- certificate/certificateModule.py | 1 - 1 file changed, 1 deletion(-) diff --git a/certificate/certificateModule.py b/certificate/certificateModule.py index 23599e4..450be52 100644 --- a/certificate/certificateModule.py +++ b/certificate/certificateModule.py @@ -29,7 +29,6 @@ def getContextVariables(self): except FileNotFoundError: print('I could not find contextVariables.json') - def getCertificate(self, __hostname, __port): """Connect to the host and get the certificate.""" __ctx = ssl.create_default_context() From d44bfccd69bfd4dae40b7892ecad6cd982499364 Mon Sep 17 00:00:00 2001 From: Git Date: Sat, 15 Apr 2023 15:36:04 -0700 Subject: [PATCH 26/44] Fixing syntax --- certCheck.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/certCheck.py b/certCheck.py index fa30407..d9ac789 100644 --- a/certCheck.py +++ b/certCheck.py @@ -271,9 +271,7 @@ def processQueryFile(): # Connect to the hostname from the queryFile argument and get the certificate associated with it. myCertificate = o_myCertificate.getCertificate(myHostname["hostname"], myHostname["port"]) - if myCertificate["certificateMetaData"] is not None: - break - else: + if myCertificate["certificateMetaData"] is None: # If unable to connect to host for whatever reason, pause for a second then try again. time.sleep(int(args.timeBetweenRetries)) @@ -339,9 +337,7 @@ def processHostname(): # Connect to the hostname from the queryFile argument and get the certificate associated with it. myCertificate = o_myCertificate.getCertificate(hostnameQuery["hostname"], hostnameQuery["port"]) - if myCertificate["certificateMetaData"] is not None: - break - else: + if myCertificate["certificateMetaData"] is None: # If unable to connect to host for whatever reason, pause for a second then try again. time.sleep(int(args.timeBetweenRetries)) From 8239b55f9fb6f0bf6421b4cb1dc68fa1864c72c8 Mon Sep 17 00:00:00 2001 From: Git Date: Sat, 15 Apr 2023 15:42:37 -0700 Subject: [PATCH 27/44] Fixing syntax. --- certificate/certificateModule.py | 1 + data/sendDataMongoDB.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/certificate/certificateModule.py b/certificate/certificateModule.py index 450be52..9fb91ee 100644 --- a/certificate/certificateModule.py +++ b/certificate/certificateModule.py @@ -34,6 +34,7 @@ def getCertificate(self, __hostname, __port): __ctx = ssl.create_default_context() if self.contextVariables is not None: + # If securityLevel is set if self.contextVariables["securityLevel"] == 1: # Lower the default security level __ctx.set_ciphers('DEFAULT@SECLEVEL=1') diff --git a/data/sendDataMongoDB.py b/data/sendDataMongoDB.py index 943c624..762a29c 100644 --- a/data/sendDataMongoDB.py +++ b/data/sendDataMongoDB.py @@ -60,7 +60,7 @@ def getJsonScriptDataFromFile(__fileName): try: with open("certificateData.json", "r") as fileJsonScriptData: while True: - fileLine = fileJsonScriptData.readline().replace("\n","") + fileLine = fileJsonScriptData.readline().replace("\n", "") if not fileLine: break From 91dc9d43756606cf337a7abb43f10325ff397a3b Mon Sep 17 00:00:00 2001 From: Git Date: Sat, 15 Apr 2023 15:47:25 -0700 Subject: [PATCH 28/44] Fixing syntax. --- certCheck.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/certCheck.py b/certCheck.py index d9ac789..425d7cb 100644 --- a/certCheck.py +++ b/certCheck.py @@ -267,7 +267,7 @@ def processQueryFile(): o_startTime = datetime.datetime.utcnow() # Iterate through number of retryAmount - for counter in range(int(args.retryAmount)): + for _ in range(int(args.retryAmount)): # Connect to the hostname from the queryFile argument and get the certificate associated with it. myCertificate = o_myCertificate.getCertificate(myHostname["hostname"], myHostname["port"]) @@ -333,7 +333,7 @@ def processHostname(): hostnameQuery = {"hostname": args.hostname, "port": 443} # Iterate through number of retryAmount - for counter in range(int(args.retryAmount)): + for _ in range(int(args.retryAmount)): # Connect to the hostname from the queryFile argument and get the certificate associated with it. myCertificate = o_myCertificate.getCertificate(hostnameQuery["hostname"], hostnameQuery["port"]) From ade61a348e0fcfebc18600bcde647bf8744666d8 Mon Sep 17 00:00:00 2001 From: Git Date: Thu, 18 May 2023 21:42:51 -0700 Subject: [PATCH 29/44] Version 0.39. See CHANGELOG.md --- CHANGELOG.md | 5 +++++ README.md | 4 ++-- certCheck.py | 6 +++--- data/sendDataMongoDB.py | 10 +++++----- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b3ea8e..7350c13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 2023/05/18 +## Version 0.39 +### Fixes +* [MongoDB bugfix](https://github.com/TheScriptGuy/certificateChecker/issues/15) + # 2023/04/15 ## Version 0.38 ### Feature additions diff --git a/README.md b/README.md index f90b5a3..8cf7610 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Certificate Checker -Version: 0.38 +Version: 0.39 Author: TheScriptGuy @@ -30,7 +30,7 @@ usage: certCheck.py [-h] [--hostname HOSTNAME] [--displayCertificate] [--display [--sendEmail] [--retryAmount RETRYAMOUNT] [--timeBetweenRetries TIMEBETWEENRETRIES] [--contextVariables] [--environmentVariables] [--setTag SETTAG] [--delTag] [--getTag] [--renewDeviceId] [--getDeviceId] [--deleteDeviceId] [--setTenantId SETTENANTID] [--getTenantId] [--delTenantId] [--createBlankConfiguration] -Certificate Checker v0.38 +Certificate Checker v0.39 optional arguments: -h, --help show this help message and exit diff --git a/certCheck.py b/certCheck.py index 425d7cb..ae4956f 100644 --- a/certCheck.py +++ b/certCheck.py @@ -1,7 +1,7 @@ # Program: Certificate Checker # Author: Nolan Rumble -# Date: 2023/04/15 -# Version: 0.38 +# Date: 2023/05/18 +# Version: 0.39 import argparse import datetime @@ -18,7 +18,7 @@ from data import emailTemplateBuilder from data import sendDataEmail -scriptVersion = "0.38" +scriptVersion = "0.39" # Global Variables args = None diff --git a/data/sendDataMongoDB.py b/data/sendDataMongoDB.py index 762a29c..068254d 100644 --- a/data/sendDataMongoDB.py +++ b/data/sendDataMongoDB.py @@ -1,7 +1,7 @@ # Class: sendDataMongoDB -# Last updated: 2023/04/15 +# Last updated: 2023/05/18 # Author: TheScriptGuy (https://github.com/TheScriptGuy) -# Version: 0.10 +# Version: 0.11 # Description: Send json list to mongoDB based on configuration in mongo.cfg import pymongo @@ -108,7 +108,7 @@ def sendResults(self, __results, __destCollection): __uploadResult.append(__mongoResult) except pymongo.errors.ServerSelectionTimeoutError: # Get time of error - errTime = str(datetime.datetime.utcnow()) + errTime = str(datetime.utcnow()) print(f"{errTime} - Server connection timeout error when uploading data. Saving to certificateData.json") # Save test data to file. @@ -117,7 +117,7 @@ def sendResults(self, __results, __destCollection): except pymongo.errors.OperationFailure as e: # Get time of error - errTime = str(datetime.datetime.utcnow()) + errTime = str(datetime.utcnow()) print(f"{errTime} - Mongo operation error - {e}. Saving to certificateData.json") # Save test data to file. @@ -240,4 +240,4 @@ def uploadDataToMongoDB(self, __jsonScriptData): def __init__(self): """Initialize the sendDataMongoDB class.""" self.initialized = True - self.version = "0.10" + self.version = "0.11" From 27bd8060f9aa2b4304f401243b9e6ca3d4206fe9 Mon Sep 17 00:00:00 2001 From: Git Date: Mon, 22 May 2023 15:39:19 -0700 Subject: [PATCH 30/44] Version 0.40. See CHANGELOG.md --- CHANGELOG.md | 6 ++- README-queryFile.md | 25 ++++++++++++ README.md | 3 +- certCheck.py | 12 +++--- certificate/certificateModule.py | 30 +++++++++----- data/certData.py | 68 ++++++++++++++++++++------------ 6 files changed, 100 insertions(+), 44 deletions(-) create mode 100644 README-queryFile.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 7350c13..fadad51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,8 @@ -# 2023/05/18 +# 2023/05/22 +## Version 0.40 +### Feature Additions/Enhancements +* Adding the ability to use unsafe legacy method of connection. See examples here - [Example of queryFile structure](https://github.com/TheScriptGuy/certificateChecker/blob/main/README-queryFile.md) + ## Version 0.39 ### Fixes * [MongoDB bugfix](https://github.com/TheScriptGuy/certificateChecker/issues/15) diff --git a/README-queryFile.md b/README-queryFile.md new file mode 100644 index 0000000..c7945d3 --- /dev/null +++ b/README-queryFile.md @@ -0,0 +1,25 @@ +# queryFile structure +## Background +With "recent" upgrades in openssl to by default not accept legacy renegotiation, some websites don't like this an error out. +The queryFile structure has been updated to allow (on a host-by-host basis) a legacy renegotiation to take place. + +## Acceptable File syntax: +``` +hostname +hostname, +hostname:port +hostname:port, +hostname,[] +hostname:port,[] +``` + +In the lines where no `[` or `]` are seen, then it's treated as `None` options provided. i.e. use defaults. + +Now in the `[` and `]`, the options that are available to today are: +`unsafe_legacy` + +To connect to a host with an option configured, these would all be considered valid examples: +``` +apple.com,['unsafe_legacy'] +apple.com:443,['unsafe_legacy'] +``` diff --git a/README.md b/README.md index 8cf7610..4fe8d22 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Certificate Checker -Version: 0.39 +Version: 0.40 Author: TheScriptGuy @@ -82,6 +82,7 @@ Now that environment variables are defined, we can use the `--environmentVariabl $ python3 certCheck.py --hostname apple.com --environmentVariables --displayScriptDataJSON ``` +[Example of queryFile structure](https://github.com/TheScriptGuy/certificateChecker/blob/main/README-queryFile.md) [Example to send an email](https://github.com/TheScriptGuy/certificateChecker/blob/main/README-email.md) diff --git a/certCheck.py b/certCheck.py index ae4956f..dea324f 100644 --- a/certCheck.py +++ b/certCheck.py @@ -1,7 +1,7 @@ # Program: Certificate Checker # Author: Nolan Rumble -# Date: 2023/05/18 -# Version: 0.39 +# Date: 2023/05/22 +# Version: 0.40 import argparse import datetime @@ -18,7 +18,7 @@ from data import emailTemplateBuilder from data import sendDataEmail -scriptVersion = "0.39" +scriptVersion = "0.40" # Global Variables args = None @@ -254,7 +254,7 @@ def processQueryFile(): scriptStartTime = datetime.datetime.utcnow() for myHostname in myCertData.loadQueriesFile(args.queryFile): - + contextVariables = 0 if args.contextVariables: @@ -269,7 +269,7 @@ def processQueryFile(): # Iterate through number of retryAmount for _ in range(int(args.retryAmount)): # Connect to the hostname from the queryFile argument and get the certificate associated with it. - myCertificate = o_myCertificate.getCertificate(myHostname["hostname"], myHostname["port"]) + myCertificate = o_myCertificate.getCertificate(myHostname) if myCertificate["certificateMetaData"] is None: # If unable to connect to host for whatever reason, pause for a second then try again. @@ -335,7 +335,7 @@ def processHostname(): # Iterate through number of retryAmount for _ in range(int(args.retryAmount)): # Connect to the hostname from the queryFile argument and get the certificate associated with it. - myCertificate = o_myCertificate.getCertificate(hostnameQuery["hostname"], hostnameQuery["port"]) + myCertificate = o_myCertificate.getCertificate(hostnameQuery) if myCertificate["certificateMetaData"] is None: # If unable to connect to host for whatever reason, pause for a second then try again. diff --git a/certificate/certificateModule.py b/certificate/certificateModule.py index 9fb91ee..c10b4b8 100644 --- a/certificate/certificateModule.py +++ b/certificate/certificateModule.py @@ -1,6 +1,6 @@ # Certificate Module1 -# Version: 0.13 -# Last updated: 2023-02-05 +# Version: 0.14 +# Last updated: 2023-05-22 # Author: TheScriptGuy import ssl @@ -29,10 +29,19 @@ def getContextVariables(self): except FileNotFoundError: print('I could not find contextVariables.json') - def getCertificate(self, __hostname, __port): + def getCertificate(self, __hostinfo): """Connect to the host and get the certificate.""" + + # Create the default context. __ctx = ssl.create_default_context() + # Check to see if there are any options that need to be passed for the connection + if __hostinfo['options'] is not None: + for ssl_options in __hostinfo['options']: + if ssl_options == "unsafe_legacy": + __ctx.options |= 0x4 # OP_LEGACY_SERVER_CONNECT + + # If there are any global options that need to be set. if self.contextVariables is not None: # If securityLevel is set if self.contextVariables["securityLevel"] == 1: @@ -46,31 +55,32 @@ def getCertificate(self, __hostname, __port): } try: - with __ctx.wrap_socket(socket.socket(), server_hostname=__hostname) as s: - s.connect((__hostname, __port)) + with __ctx.wrap_socket(socket.socket(), server_hostname=__hostinfo['hostname']) as s: + + s.connect((__hostinfo['hostname'], __hostinfo['port'])) __certificate = s.getpeercert() __cipher = s.cipher() __hostnameData["certificateMetaData"] = __certificate __hostnameData["connectionCipher"] = __cipher except ssl.SSLCertVerificationError as e: - connectHost = __hostname + ":" + str(__port) + connectHost = f"{__hostinfo['hostname']}:{__hostinfo['port']}, options: {__hostinfo['options']}" print(connectHost + ' - Certificate error - ', e.verify_message) except socket.gaierror as e: - connectHost = __hostname + ":" + str(__port) + connectHost = f"{__hostinfo['hostname']}:{__hostinfo['port']}, options: {__hostinfo['options']}" print(connectHost + ' - Socket error - ', e.strerror) except FileNotFoundError as e: - connectHost = __hostname + ":" + str(__port) + connectHost = f"{__hostinfo['hostname']}:{__hostinfo['port']}, options: {__hostinfo['options']}" print(connectHost + ' - File not found - ', e.strerror) except TimeoutError as e: - connectHost = __hostname + ":" + str(__port) + connectHost = f"{__hostinfo['hostname']}:{__hostinfo['port']}, options: {__hostinfo['options']}" print(connectHost + ' - Timeout error - ', e.strerror) except OSError as e: - connectHost = __hostname + ":" + str(__port) + connectHost = f"{__hostinfo['hostname']}:{__hostinfo['port']}, options: {__hostinfo['options']}" print(connectHost + ' - OSError - ', e.strerror) return __hostnameData diff --git a/data/certData.py b/data/certData.py index b262945..6aa75ba 100644 --- a/data/certData.py +++ b/data/certData.py @@ -1,11 +1,14 @@ -# Certificate Data Handling -# Version: 0.06 +# Description: Certificate Data Handling +# Author: TheScriptGuy +# Version: 0.07 +# Last modified: 2023/05/22 + import sys from os import path import requests import socket - +import ast class certData: """certData class""" @@ -52,6 +55,31 @@ def uploadJsonHTTP(url, jsonData): x = requests.post(url, json=jsonData) return x.headers + @staticmethod + def parse_line(line): + # Default values + hostname = '' + port = 443 + options = None + + # Remove newline character if it exists + line = line.rstrip() + + # Check if options exist + if '[' in line: + line, options = line.split('[', 1) + # Remove the closing bracket and convert to a list + options = ast.literal_eval('[' + options) + + # Check if port number exists + if ':' in line: + hostname, port = line.split(':') + port = int(port.rstrip(',')) + else: + hostname = line.rstrip(',') + + return {"hostname": hostname, "port": port, "options": options} + @staticmethod def loadQueriesFile(queriesFile): """ @@ -64,33 +92,21 @@ def loadQueriesFile(queriesFile): if queriesFile.startswith('http://') or queriesFile.startswith('https://'): myQueries = certData.getFileFromURL(queriesFile) for line in myQueries: - if ":" in line: - tmpLine = line.split(':') - queries.append({"hostname": tmpLine[0], "port": int(tmpLine[1])}) - else: - queries.append({"hostname": line, "port": 443}) - - return queries + hostEntry = certData.parse_line(line) + queries.append(hostEntry) - # Check to see if if the file exists. If not, exit with error code 1. - if not path.exists(queriesFile): - print('I cannot find file ' + queriesFile) + elif path.exists(queriesFile) and not (queriesFile.startswith('http://') or queriesFile.startswith('https://')): + with open(queriesFile, "r", encoding="utf-8") as f_queryFile: + queryFile = f_queryFile.readlines() + for line in queryFile: + hostEntry = certData.parse_line(line) + queries.append(hostEntry) + else: + print('I cannot get file ' + queriesFile) sys.exit(1) - - queryFile = open(queriesFile, "r", encoding="utf-8") - - for line in queryFile: - if ":" in line: - tmpLine = line.rstrip('\n').split(':') - queries.append({"hostname": tmpLine[0], "port": int(tmpLine[1])}) - else: - queries.append({"hostname": line.rstrip('\n'), "port": 443}) - - queryFile.close() - return queries def __init__(self): """Initialize the certData class.""" self.initialized = True - self.version = "0.06" + self.version = "0.07" From 122f057532fb3e7ce3ce930cc97d40ed59f7bc72 Mon Sep 17 00:00:00 2001 From: Git Date: Mon, 22 May 2023 16:05:37 -0700 Subject: [PATCH 31/44] Version 0.41. See CHANGELOG.md --- CHANGELOG.md | 4 ++++ README-queryFile.md | 13 ++++++++++++- certCheck.py | 4 ++-- certificate/certificateModule.py | 7 +++++-- 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fadad51..f09a8e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,8 @@ # 2023/05/22 +## Version 0.41 +### Feature Additions/Enhancements +* Add the ability to ignore ssl warnings on a host by host basis. See examples here - [Example of queryFile structure](https://github.com/TheScriptGuy/certificateChecker/blob/main/README-queryFile.md) + ## Version 0.40 ### Feature Additions/Enhancements * Adding the ability to use unsafe legacy method of connection. See examples here - [Example of queryFile structure](https://github.com/TheScriptGuy/certificateChecker/blob/main/README-queryFile.md) diff --git a/README-queryFile.md b/README-queryFile.md index c7945d3..22a3de0 100644 --- a/README-queryFile.md +++ b/README-queryFile.md @@ -16,10 +16,21 @@ hostname:port,[] In the lines where no `[` or `]` are seen, then it's treated as `None` options provided. i.e. use defaults. Now in the `[` and `]`, the options that are available to today are: -`unsafe_legacy` +* `unsafe_legacy` - this allows for legacy renegotiation +* `local_untrusted_allow` - this prevents chain validation. Useful for when websites are misconfigured and presenting the full certificate chain. To connect to a host with an option configured, these would all be considered valid examples: ``` apple.com,['unsafe_legacy'] apple.com:443,['unsafe_legacy'] ``` + +Another example: +``` +apple.com,['local_untrusted_allow'] +apple.com:443,['local_untrusted_allow'] +apple.com,['unsafe_legacy','local_untrusted_allow'] +apple.com:443,['unsafe_legacy','local_untrusted_allow'] +``` + +As you can see, multiple options can be supported on each line. diff --git a/certCheck.py b/certCheck.py index dea324f..122a0a7 100644 --- a/certCheck.py +++ b/certCheck.py @@ -1,7 +1,7 @@ # Program: Certificate Checker # Author: Nolan Rumble # Date: 2023/05/22 -# Version: 0.40 +# Version: 0.41 import argparse import datetime @@ -18,7 +18,7 @@ from data import emailTemplateBuilder from data import sendDataEmail -scriptVersion = "0.40" +scriptVersion = "0.41" # Global Variables args = None diff --git a/certificate/certificateModule.py b/certificate/certificateModule.py index c10b4b8..26e3cd9 100644 --- a/certificate/certificateModule.py +++ b/certificate/certificateModule.py @@ -1,5 +1,5 @@ # Certificate Module1 -# Version: 0.14 +# Version: 0.15 # Last updated: 2023-05-22 # Author: TheScriptGuy @@ -40,6 +40,9 @@ def getCertificate(self, __hostinfo): for ssl_options in __hostinfo['options']: if ssl_options == "unsafe_legacy": __ctx.options |= 0x4 # OP_LEGACY_SERVER_CONNECT + if ssl_options == "local_untrusted_allow": + __ctx.check_hostname = False + __ctx.verify_mode = ssl.CERT_NONE # If there are any global options that need to be set. if self.contextVariables is not None: @@ -423,7 +426,7 @@ def uploadJsonData(self, __certificateJsonData, __httpUrl): def __init__(self, __contextVariables=0): """Initialize the class.""" self.initialized = True - self.moduleVersion = "0.13" + self.moduleVersion = "0.15" self.certificate = {} if __contextVariables == 1: self.contextVariables = self.getContextVariables() From c117a89d55bb204fc6b7ab81a634c161711ba70f Mon Sep 17 00:00:00 2001 From: Git Date: Mon, 22 May 2023 16:42:16 -0700 Subject: [PATCH 32/44] Minor bug fix with --hostname --- certCheck.py | 9 ++------- certificate/certificateModule.py | 2 +- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/certCheck.py b/certCheck.py index 122a0a7..4ae8af8 100644 --- a/certCheck.py +++ b/certCheck.py @@ -320,23 +320,18 @@ def processHostname(): contextVariables = 0 o_myCertificate = certificateModule.certificateModule(contextVariables) + o_myCertData = certData.certData() # For SSL performance measurement - START o_startTime = datetime.datetime.utcnow() # Connect to the hostname from the --hostname argument and get the certificate associated with it. - if ":" in args.hostname: - tmpLine = args.hostname.split(':') - hostnameQuery = {"hostname": tmpLine[0], "port": int(tmpLine[1])} - - else: - hostnameQuery = {"hostname": args.hostname, "port": 443} + hostnameQuery = o_myCertData.parse_line(args.hostname) # Iterate through number of retryAmount for _ in range(int(args.retryAmount)): # Connect to the hostname from the queryFile argument and get the certificate associated with it. myCertificate = o_myCertificate.getCertificate(hostnameQuery) - if myCertificate["certificateMetaData"] is None: # If unable to connect to host for whatever reason, pause for a second then try again. time.sleep(int(args.timeBetweenRetries)) diff --git a/certificate/certificateModule.py b/certificate/certificateModule.py index 26e3cd9..892cf4e 100644 --- a/certificate/certificateModule.py +++ b/certificate/certificateModule.py @@ -34,7 +34,6 @@ def getCertificate(self, __hostinfo): # Create the default context. __ctx = ssl.create_default_context() - # Check to see if there are any options that need to be passed for the connection if __hostinfo['options'] is not None: for ssl_options in __hostinfo['options']: @@ -351,6 +350,7 @@ def convertCertificateObject2Json(self, __hostname, __port, __startTime, __endTi if 'subject' in certKeys: myJsonCertificateInfo["certificateInfo"]["subject"] = dict(x[0] for x in __certificateObject["certificateMetaData"]["subject"]) + print(__certificateObject["certificateMetaData"]) myJsonCertificateInfo["certificateInfo"]["certificateIssuer"] = dict(x[0] for x in __certificateObject["certificateMetaData"]["issuer"]) myJsonCertificateInfo["certificateInfo"]["version"] = __certificateObject["certificateMetaData"]["version"] From 9d0a92a95d8c92b89f2b0368459edcdd271aed17 Mon Sep 17 00:00:00 2001 From: Git Date: Wed, 31 May 2023 15:10:40 -0700 Subject: [PATCH 33/44] Version 0.42. See CHANGELOG.md --- CHANGELOG.md | 5 +++++ README.md | 4 ++-- certCheck.py | 6 +++--- certificate/certificateModule.py | 7 +++---- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f09a8e5..14843a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 2023/05/31 +## Version 0.42 +### Fixes +* A left over print statement from the previous build. + # 2023/05/22 ## Version 0.41 ### Feature Additions/Enhancements diff --git a/README.md b/README.md index 4fe8d22..ed2dcbb 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Certificate Checker -Version: 0.40 +Version: 0.42 Author: TheScriptGuy @@ -30,7 +30,7 @@ usage: certCheck.py [-h] [--hostname HOSTNAME] [--displayCertificate] [--display [--sendEmail] [--retryAmount RETRYAMOUNT] [--timeBetweenRetries TIMEBETWEENRETRIES] [--contextVariables] [--environmentVariables] [--setTag SETTAG] [--delTag] [--getTag] [--renewDeviceId] [--getDeviceId] [--deleteDeviceId] [--setTenantId SETTENANTID] [--getTenantId] [--delTenantId] [--createBlankConfiguration] -Certificate Checker v0.39 +Certificate Checker v0.42 optional arguments: -h, --help show this help message and exit diff --git a/certCheck.py b/certCheck.py index 4ae8af8..837a897 100644 --- a/certCheck.py +++ b/certCheck.py @@ -1,7 +1,7 @@ # Program: Certificate Checker # Author: Nolan Rumble -# Date: 2023/05/22 -# Version: 0.41 +# Date: 2023/05/31 +# Version: 0.42 import argparse import datetime @@ -18,7 +18,7 @@ from data import emailTemplateBuilder from data import sendDataEmail -scriptVersion = "0.41" +scriptVersion = "0.42" # Global Variables args = None diff --git a/certificate/certificateModule.py b/certificate/certificateModule.py index 892cf4e..e2e62bb 100644 --- a/certificate/certificateModule.py +++ b/certificate/certificateModule.py @@ -1,6 +1,6 @@ # Certificate Module1 -# Version: 0.15 -# Last updated: 2023-05-22 +# Version: 0.16 +# Last updated: 2023-05-31 # Author: TheScriptGuy import ssl @@ -350,7 +350,6 @@ def convertCertificateObject2Json(self, __hostname, __port, __startTime, __endTi if 'subject' in certKeys: myJsonCertificateInfo["certificateInfo"]["subject"] = dict(x[0] for x in __certificateObject["certificateMetaData"]["subject"]) - print(__certificateObject["certificateMetaData"]) myJsonCertificateInfo["certificateInfo"]["certificateIssuer"] = dict(x[0] for x in __certificateObject["certificateMetaData"]["issuer"]) myJsonCertificateInfo["certificateInfo"]["version"] = __certificateObject["certificateMetaData"]["version"] @@ -426,7 +425,7 @@ def uploadJsonData(self, __certificateJsonData, __httpUrl): def __init__(self, __contextVariables=0): """Initialize the class.""" self.initialized = True - self.moduleVersion = "0.15" + self.moduleVersion = "0.16" self.certificate = {} if __contextVariables == 1: self.contextVariables = self.getContextVariables() From 2b78d0e247cf368bbaec12a9f7a7e2caaf1f12a1 Mon Sep 17 00:00:00 2001 From: Git Date: Wed, 31 May 2023 15:19:17 -0700 Subject: [PATCH 34/44] fixing syntax. --- certCheck.py | 10 +++++++--- data/certData.py | 8 ++++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/certCheck.py b/certCheck.py index 837a897..a0c018f 100644 --- a/certCheck.py +++ b/certCheck.py @@ -125,7 +125,6 @@ def defineInfoArguments(o_systemInfo): print('Environment variables TENANT_ID and TAGS are not both set.') sys.exit(1) - # If setTag argument is set, create the new Tag. if args.setTag: o_systemInfo.setTag(args.setTag, True) @@ -254,9 +253,10 @@ def processQueryFile(): scriptStartTime = datetime.datetime.utcnow() for myHostname in myCertData.loadQueriesFile(args.queryFile): - + # Set contectVariables to zero contextVariables = 0 + # Check to see if contextVariables argument was passed. if args.contextVariables: contextVariables = 1 @@ -281,12 +281,16 @@ def processQueryFile(): # Convert the certificate object into JSON format. jsonCertificateInfo = o_myCertificate.convertCertificateObject2Json(myHostname["hostname"], myHostname["port"], o_startTime, o_endTime, myCertificate) + # Append jsonCertificateInfo to jsonScriptData jsonScriptData.append(jsonCertificateInfo) + # Check to see if additional arguments were passed checkArguments(myCertificate, jsonCertificateInfo) + # Get the time the script stopped gathering data. scriptEndTime = datetime.datetime.utcnow() + # Combine all the data into a dict. myJsonScriptData = gatherData(jsonScriptData, o_myInfo, scriptStartTime, scriptEndTime) if args.displayScriptDataJSON: @@ -317,7 +321,7 @@ def processHostname(): if args.contextVariables: contextVariables = 1 else: - contextVariables = 0 + contextVariables = 0 o_myCertificate = certificateModule.certificateModule(contextVariables) o_myCertData = certData.certData() diff --git a/data/certData.py b/data/certData.py index 6aa75ba..f18b1e2 100644 --- a/data/certData.py +++ b/data/certData.py @@ -1,8 +1,7 @@ # Description: Certificate Data Handling # Author: TheScriptGuy -# Version: 0.07 -# Last modified: 2023/05/22 - +# Version: 0.08 +# Last modified: 2023/05/31 import sys from os import path @@ -10,6 +9,7 @@ import socket import ast + class certData: """certData class""" @@ -109,4 +109,4 @@ def loadQueriesFile(queriesFile): def __init__(self): """Initialize the certData class.""" self.initialized = True - self.version = "0.07" + self.version = "0.08" From 1e3beb1956d100637dc7045f01892ef283a882f4 Mon Sep 17 00:00:00 2001 From: Git Date: Wed, 31 May 2023 15:22:07 -0700 Subject: [PATCH 35/44] fixing syntax. --- certificate/certificateModule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/certificate/certificateModule.py b/certificate/certificateModule.py index e2e62bb..d85dc63 100644 --- a/certificate/certificateModule.py +++ b/certificate/certificateModule.py @@ -193,7 +193,7 @@ def checkTimeValidity(self, __certificateObject): certNotBefore = datetime.datetime.strptime(self.returnNotBefore(__certificateObject), self.certTimeFormat).date() # Assume time not valid - isValid = bool((certNotBefore < timeNow) and (certNotAfter > timeNow)) + isValid = bool(certNotBefore < timeNow < certNotAfter) return isValid return False From 85be754fd345f375eb8c0a73ebeffe7bed852640 Mon Sep 17 00:00:00 2001 From: Git Date: Sat, 24 Jun 2023 12:07:24 -0700 Subject: [PATCH 36/44] Version 0.43. See CHANGELOG.md --- CHANGELOG.md | 5 +++++ README.md | 4 ++-- certCheck.py | 6 +++--- certificate/certificateModule.py | 22 +++++++++++++--------- 4 files changed, 23 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14843a4..cd10932 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 2023/06/24 +## Version 0.43 +### Enhancements +* [Socket timeout value](https://github.com/TheScriptGuy/certificateChecker/issues/17). + # 2023/05/31 ## Version 0.42 ### Fixes diff --git a/README.md b/README.md index ed2dcbb..ef3ae79 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Certificate Checker -Version: 0.42 +Version: 0.43 Author: TheScriptGuy @@ -30,7 +30,7 @@ usage: certCheck.py [-h] [--hostname HOSTNAME] [--displayCertificate] [--display [--sendEmail] [--retryAmount RETRYAMOUNT] [--timeBetweenRetries TIMEBETWEENRETRIES] [--contextVariables] [--environmentVariables] [--setTag SETTAG] [--delTag] [--getTag] [--renewDeviceId] [--getDeviceId] [--deleteDeviceId] [--setTenantId SETTENANTID] [--getTenantId] [--delTenantId] [--createBlankConfiguration] -Certificate Checker v0.42 +Certificate Checker v0.43 optional arguments: -h, --help show this help message and exit diff --git a/certCheck.py b/certCheck.py index a0c018f..4307623 100644 --- a/certCheck.py +++ b/certCheck.py @@ -1,7 +1,7 @@ # Program: Certificate Checker # Author: Nolan Rumble -# Date: 2023/05/31 -# Version: 0.42 +# Date: 2023/06/24 +# Version: 0.43 import argparse import datetime @@ -18,7 +18,7 @@ from data import emailTemplateBuilder from data import sendDataEmail -scriptVersion = "0.42" +scriptVersion = "0.43" # Global Variables args = None diff --git a/certificate/certificateModule.py b/certificate/certificateModule.py index d85dc63..f575be9 100644 --- a/certificate/certificateModule.py +++ b/certificate/certificateModule.py @@ -1,6 +1,6 @@ # Certificate Module1 -# Version: 0.16 -# Last updated: 2023-05-31 +# Version: 0.17 +# Last updated: 2023-06-24 # Author: TheScriptGuy import ssl @@ -34,6 +34,7 @@ def getCertificate(self, __hostinfo): # Create the default context. __ctx = ssl.create_default_context() + # Check to see if there are any options that need to be passed for the connection if __hostinfo['options'] is not None: for ssl_options in __hostinfo['options']: @@ -57,13 +58,16 @@ def getCertificate(self, __hostinfo): } try: - with __ctx.wrap_socket(socket.socket(), server_hostname=__hostinfo['hostname']) as s: - - s.connect((__hostinfo['hostname'], __hostinfo['port'])) - __certificate = s.getpeercert() - __cipher = s.cipher() - __hostnameData["certificateMetaData"] = __certificate - __hostnameData["connectionCipher"] = __cipher + # Create a new socket. + with socket.socket() as sock: + # Set timeout value for socket to 10 seconds. + sock.settimeout(10.0) + with __ctx.wrap_socket(sock, server_hostname=__hostinfo['hostname']) as s: + s.connect((__hostinfo['hostname'], __hostinfo['port'])) + __certificate = s.getpeercert() + __cipher = s.cipher() + __hostnameData["certificateMetaData"] = __certificate + __hostnameData["connectionCipher"] = __cipher except ssl.SSLCertVerificationError as e: connectHost = f"{__hostinfo['hostname']}:{__hostinfo['port']}, options: {__hostinfo['options']}" From 8a868cf64b384f47ef868365e6895bac2962a756 Mon Sep 17 00:00:00 2001 From: Git Date: Sat, 24 Jun 2023 12:09:15 -0700 Subject: [PATCH 37/44] Version 0.43. See CHANGELOG.md --- certificate/certificateModule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/certificate/certificateModule.py b/certificate/certificateModule.py index f575be9..8a86959 100644 --- a/certificate/certificateModule.py +++ b/certificate/certificateModule.py @@ -58,7 +58,7 @@ def getCertificate(self, __hostinfo): } try: - # Create a new socket. + # Create a new socket. with socket.socket() as sock: # Set timeout value for socket to 10 seconds. sock.settimeout(10.0) From b67d11185628e72c5df7dc1a2b0ec9ed9b85d116 Mon Sep 17 00:00:00 2001 From: Git Date: Sat, 8 Jul 2023 18:36:47 -0700 Subject: [PATCH 38/44] Version 0.44. See CHANGELOG.md --- CHANGELOG.md | 5 +++++ README.md | 4 ++-- certCheck.py | 6 ++--- certificate/certificateModule.py | 38 ++++++++++++++++++++++---------- 4 files changed, 36 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd10932..886576b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 2023/07/08 +## Version 0.44 +### Fixes +* [traceback untrusted issuer](https://github.com/TheScriptGuy/certificateChecker/issues/19). + # 2023/06/24 ## Version 0.43 ### Enhancements diff --git a/README.md b/README.md index ef3ae79..a85ed85 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Certificate Checker -Version: 0.43 +Version: 0.44 Author: TheScriptGuy @@ -30,7 +30,7 @@ usage: certCheck.py [-h] [--hostname HOSTNAME] [--displayCertificate] [--display [--sendEmail] [--retryAmount RETRYAMOUNT] [--timeBetweenRetries TIMEBETWEENRETRIES] [--contextVariables] [--environmentVariables] [--setTag SETTAG] [--delTag] [--getTag] [--renewDeviceId] [--getDeviceId] [--deleteDeviceId] [--setTenantId SETTENANTID] [--getTenantId] [--delTenantId] [--createBlankConfiguration] -Certificate Checker v0.43 +Certificate Checker v0.44 optional arguments: -h, --help show this help message and exit diff --git a/certCheck.py b/certCheck.py index 4307623..1781e68 100644 --- a/certCheck.py +++ b/certCheck.py @@ -1,7 +1,7 @@ # Program: Certificate Checker # Author: Nolan Rumble -# Date: 2023/06/24 -# Version: 0.43 +# Date: 2023/07/08 +# Version: 0.44 import argparse import datetime @@ -18,7 +18,7 @@ from data import emailTemplateBuilder from data import sendDataEmail -scriptVersion = "0.43" +scriptVersion = "0.44" # Global Variables args = None diff --git a/certificate/certificateModule.py b/certificate/certificateModule.py index 8a86959..507fc7a 100644 --- a/certificate/certificateModule.py +++ b/certificate/certificateModule.py @@ -1,6 +1,6 @@ -# Certificate Module1 -# Version: 0.17 -# Last updated: 2023-06-24 +# Certificate Module +# Version: 0.18 +# Last updated: 2023-07-08 # Author: TheScriptGuy import ssl @@ -8,6 +8,9 @@ import datetime import json import requests +import hashlib +import os +from . import getCertificateChain from dateutil.relativedelta import relativedelta @@ -32,17 +35,28 @@ def getContextVariables(self): def getCertificate(self, __hostinfo): """Connect to the host and get the certificate.""" - # Create the default context. - __ctx = ssl.create_default_context() + # Determine which context to create + if __hostinfo['options'] is not None and "local_untrusted_allow" in __hostinfo['options']: + hostnamePortPair = f'{__hostinfo["hostname"]}:{__hostinfo["port"]}' + certificateHashFilename = hashlib.sha256(hostnamePortPair.encode()).hexdigest() + ".pem" + + if not os.path.exists(certificateHashFilename): + # Try to build the chain + occ = getCertificateChain.getCertificateChain() + occ.getCertificateChain(__hostinfo['hostname'],__hostinfo['port']) + + __ctx = ssl.create_default_context(cafile=certificateHashFilename) + else: + # Create the default context. + __ctx = ssl.create_default_context() # Check to see if there are any options that need to be passed for the connection if __hostinfo['options'] is not None: - for ssl_options in __hostinfo['options']: - if ssl_options == "unsafe_legacy": - __ctx.options |= 0x4 # OP_LEGACY_SERVER_CONNECT - if ssl_options == "local_untrusted_allow": - __ctx.check_hostname = False - __ctx.verify_mode = ssl.CERT_NONE + if "unsafe_legacy" in __hostinfo['options']: + __ctx.options |= 0x4 # OP_LEGACY_SERVER_CONNECT + if "local_untrusted_allow" in __hostinfo['options']: + __ctx.check_hostname = False + __ctx.verify_mode = ssl.CERT_OPTIONAL # If there are any global options that need to be set. if self.contextVariables is not None: @@ -429,7 +443,7 @@ def uploadJsonData(self, __certificateJsonData, __httpUrl): def __init__(self, __contextVariables=0): """Initialize the class.""" self.initialized = True - self.moduleVersion = "0.16" + self.moduleVersion = "0.18" self.certificate = {} if __contextVariables == 1: self.contextVariables = self.getContextVariables() From 50932ed228916797d79e3b5b3d92ff4dab0584b7 Mon Sep 17 00:00:00 2001 From: Git Date: Sat, 8 Jul 2023 18:39:54 -0700 Subject: [PATCH 39/44] Version 0.44. See CHANGELOG.md --- certificate/certificateModule.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/certificate/certificateModule.py b/certificate/certificateModule.py index 507fc7a..d9f4f80 100644 --- a/certificate/certificateModule.py +++ b/certificate/certificateModule.py @@ -43,8 +43,8 @@ def getCertificate(self, __hostinfo): if not os.path.exists(certificateHashFilename): # Try to build the chain occ = getCertificateChain.getCertificateChain() - occ.getCertificateChain(__hostinfo['hostname'],__hostinfo['port']) - + occ.getCertificateChain(__hostinfo['hostname'], __hostinfo['port']) + __ctx = ssl.create_default_context(cafile=certificateHashFilename) else: # Create the default context. From f525d6c4c4009a29cec975bbc20759a93aed3408 Mon Sep 17 00:00:00 2001 From: Git Date: Sat, 8 Jul 2023 18:56:36 -0700 Subject: [PATCH 40/44] Version 0.44. See CHANGELOG.md --- certificate/getCertificateChain.py | 310 +++++++++++++++++++++++++++++ 1 file changed, 310 insertions(+) create mode 100644 certificate/getCertificateChain.py diff --git a/certificate/getCertificateChain.py b/certificate/getCertificateChain.py new file mode 100644 index 0000000..f93e295 --- /dev/null +++ b/certificate/getCertificateChain.py @@ -0,0 +1,310 @@ +# Description: Get the certificate chain from a website. +# Author: TheScriptGuy +# Last modified: 2023-07-08 +# Version: 0.01 + +import ssl +import socket +from cryptography import x509 +from cryptography.x509.oid import ExtensionOID +from cryptography.hazmat.primitives import hashes, serialization + +import requests +import argparse +import sys +import os +import glob +import re +import hashlib + + +class getCertificateChain: + """ + In some rare occassions where a website is poorly configured. + e.g. not presenting the full certificate chain, the python's ssl fails to connect. + This class will attempt to collect the certificate chain and store it to file for + later use. + """ + @staticmethod + def loadRootCACertChain(__filename: str) -> dict: + """ + Load the Root CA Chain in a structured format. + caRootStore = { + "Root CA Name 1": "", + "Root CA Name 2": "", + ... + } + """ + previousLine = "" + currentLine = "" + + caRootStore = {} + try: + with open(__filename, "r") as f_caCert: + while True: + previousLine = currentLine + currentLine = f_caCert.readline() + + if not currentLine: + break + + if re.search("^\={5,}", currentLine): + """ + This is where the Root CA certificate file begins. + Iterate through all the lines between + -----BEGIN CERTIFICATE----- + ... + -----END CERTIFICATE----- + """ + rootCACert = "" + rootCAName = previousLine.strip() + + while True: + caCertLine = f_caCert.readline() + if caCertLine.strip() != "-----END CERTIFICATE-----": + rootCACert += caCertLine + else: + rootCACert += "-----END CERTIFICATE-----\n" + break + + caRootStore[rootCAName] = rootCACert + + print(f"Number of Root CA's loaded: {len(caRootStore)}") + + return caRootStore + + except FileNotFoundError: + print("Could not find cacert.pem file.") + sys.exit(1) + + @staticmethod + def getCertificate(__hostname: str, __port: int): + """Retrieves the certificate from the website.""" + try: + """ + Create the SSL context. + We will ignore any certificate warnings for this process. + """ + sslContext = ssl._create_unverified_context() + + with socket.create_connection((__hostname, __port)) as sock: + with sslContext.wrap_socket(sock, server_hostname=__hostname) as sslSocket: + # Get the certificate from the connection, convert it to PEM format. + sslCertificate = ssl.DER_cert_to_PEM_cert(sslSocket.getpeercert(True)) + + # Load the PEM formatted file. + sslCertificate = x509.load_pem_x509_certificate(sslCertificate.encode('ascii')) + + except ConnectionRefusedError: + print(f"Connection refused to {__hostname}:{__port}") + sys.exit(1) + + # Return the sslCertificate object. + return sslCertificate + + @staticmethod + def getCertificateFromUri(__uri: str): + """Gets the certificate from a URI. + By default, we're expecting to find nothing. Therefore certI = None. + If we find something, we'll update certI accordingly. + """ + certI = None + + # Attempt to get the aia from __uri + aiaRequest = requests.get(__uri) + + # If response status code is 200 + if aiaRequest.status_code == 200: + # Get the content and assign to aiaContent + aiaContent = aiaRequest.content + + # Convert the certificate into PEM format. + sslCertificate = ssl.DER_cert_to_PEM_cert(aiaContent) + + # Load the PEM formatted content using x509 module. + certI = x509.load_pem_x509_certificate(sslCertificate.encode('ascii')) + + # Return certI back to the script. + return certI + + @staticmethod + def returnCertAKI(__sslCertificate): + """Returns the AKI of the certificate.""" + try: + certAKI = __sslCertificate.extensions.get_extension_for_oid(ExtensionOID.AUTHORITY_KEY_IDENTIFIER) + except x509.extensions.ExtensionNotFound: + certAKI = None + return certAKI + + @staticmethod + def returnCertSKI(__sslCertificate): + """Returns the SKI of the certificate.""" + certSKI = __sslCertificate.extensions.get_extension_for_oid(ExtensionOID.SUBJECT_KEY_IDENTIFIER) + + return certSKI + + @staticmethod + def returnCertAIA(__sslCertificate): + """Returns the AIA of the certificate. If not defined, then return None.""" + try: + certAIA = __sslCertificate.extensions.get_extension_for_oid(ExtensionOID.AUTHORITY_INFORMATION_ACCESS) + + except x509.extensions.ExtensionNotFound: + certAIA = None + + return certAIA + + @staticmethod + def returnCertAIAList(__sslCertificate): + """Returns a list of AIA's defined in __sslCertificate.""" + aiaUriList = [] + + # Iterate through all the extensions. + for extension in __sslCertificate.extensions: + certValue = extension.value + + # If the extension is x509.AuthorityInformationAccess) then lets get the caIssuers from the field. + if isinstance(certValue, x509.AuthorityInformationAccess): + dataAIA = [x for x in certValue or []] + for item in dataAIA: + if item.access_method._name == "caIssuers": + aiaUriList.append(item.access_location._value) + + # Return the aiaUriList back to the script. + return aiaUriList + + def walkTheChain(self, __sslCertificate, __depth: int): + """ + Walk the length of the chain, fetching information from AIA + along the way until AKI == SKI (i.e. we've found the Root CA. + + This is to prevent recursive loops. Usually there are only 4 certificates. + If the maxDepth is too small (why?) adjust it at the beginning of the script. + """ + + if __depth <= self.maxDepth: + # Retrive the AKI from the certificate. + certAKI = self.returnCertAKI(__sslCertificate) + # Retrieve the SKI from the certificate. + certSKI = self.returnCertSKI(__sslCertificate) + + # Sometimes the AKI can be none. Lets handle this accordingly. + if certAKI is not None: + certAKIValue = certAKI._value.key_identifier + else: + certAKIValue = None + + # Get the value of the SKI from certSKI + certSKIValue = certSKI._value.digest + + # Sometimes the AKI can be none. Lets handle this accordingly. + if certAKIValue is not None: + aiaUriList = self.returnCertAIAList(__sslCertificate) + if aiaUriList != []: + # Iterate through the aiaUriList list. + for item in aiaUriList: + # get the certificate for the item element. + nextCert = self.getCertificateFromUri(item) + + # If the certificate is not none (great), append it to the certChain, increase the __depth and run the walkTheChain subroutine again. + if nextCert is not None: + self.certChain.append(nextCert) + __depth += 1 + self.walkTheChain(nextCert, __depth) + else: + print("Could not retrieve certificate.") + sys.exit(1) + else: + """Now we have to go on a hunt to find the root from a standard root store.""" + print("Certificate didn't have AIA...ruh roh.") + + # Load the Root CA Cert Chain. + caRootStore = self.loadRootCACertChain("cacert.pem") + + # Assume we cannot find a Root CA + rootCACN = None + + # Iterate through the caRootStore object. + for rootCA in caRootStore: + try: + rootCACertificatePEM = caRootStore[rootCA] + rootCACertificate = x509.load_pem_x509_certificate(rootCACertificatePEM.encode('ascii')) + rootCASKI = self.returnCertSKI(rootCACertificate) + rootCASKI_Value = rootCASKI._value.digest + if rootCASKI_Value == certAKIValue: + rootCACN = rootCA + print(f"Root CA Found - {rootCACN}") + self.certChain.append(rootCACertificate) + break + except x509.extensions.ExtensionNotFound: + # Apparently some Root CA's don't have a SKI? + pass + + if rootCACN == None: + print("ERROR - Root CA NOT found.") + sys.exit(1) + + @staticmethod + def sendCertificateToFile(__filename: str, __sslCertificate) -> None: + """Write the certificate in PEM format to file.""" + with open(__filename, "ab") as f_clientPublicKey: + f_clientPublicKey.write( + __sslCertificate.public_bytes( + encoding=serialization.Encoding.PEM, + ) + b'\n' + ) + + def writeChainToFile(self, __certificateChain: dict) -> None: + """Write all the elements in the chain to file.""" + myCertChain = __certificateChain + + myCertChain.pop() + + # Iterate through all the elements in the chain. + for counter, certificateItem in enumerate(myCertChain): + # Get the subject from the certificate. + certSubject = certificateItem.subject.rfc4514_string() + + # Generate the certificate file name + sslCertificateFilename = f'{self.certificateHash}.pem' + + # Send the certificate object to the sslCertificateFileName filename + self.sendCertificateToFile(sslCertificateFilename, certificateItem) + + def getCertificateChain(self, __hostname: str, __port: int): + """Get Certificate Chain.""" + + # Create the hash for the __hostname:__port pair + hostnamePort = f"{__hostname}:{__port}" + + self.certificateHash = hashlib.sha256(hostnamePort.encode()).hexdigest() + + # Get the website certificate object from myHostname["hostname"]:myHostname["port"] + __websiteCertificate = self.getCertificate(__hostname, __port) + + if __websiteCertificate is not None: + # Get the AIA from the __websiteCertificate object + aia = self.returnCertAIA(__websiteCertificate) + if aia is not None: + # Extract the AIA URI list from the __websiteCertificate object. + aiaUriList = self.returnCertAIAList(__websiteCertificate) + + # Append the __websiteCertificate object to the certChain list. + self.certChain.append(__websiteCertificate) + + # Now we walk the chain up until we get the Root CA. + self.walkTheChain(__websiteCertificate,1) + + # Write the certificate chain to individual files. + self.writeChainToFile(self.certChain) + else: + print("ERROR - I could not find AIA. Possible decryption taking place upstream?") + sys.exit(1) + + def __init__(self): + """Init the getCertChain class.""" + self.classVersion = "0.01" + self.maxDepth = 4 + self.certChain = [] + self.certificateHash = "" + From a8bb774c4726de2936999c5ce6c74cedef1fb3d1 Mon Sep 17 00:00:00 2001 From: Git Date: Sun, 9 Jul 2023 12:40:52 -0700 Subject: [PATCH 41/44] Version 0.45. See CHANGELOG.md --- CHANGELOG.md | 5 +++++ README.md | 4 ++-- certCheck.py | 6 +++--- certificate/getCertificateChain.py | 8 ++++---- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 886576b..f727623 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 2023/07/09 +## Version 0.45 +### Fixes +* Fixed a minor bug with the certificate chain retrieval. + # 2023/07/08 ## Version 0.44 ### Fixes diff --git a/README.md b/README.md index a85ed85..b8166ff 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Certificate Checker -Version: 0.44 +Version: 0.45 Author: TheScriptGuy @@ -30,7 +30,7 @@ usage: certCheck.py [-h] [--hostname HOSTNAME] [--displayCertificate] [--display [--sendEmail] [--retryAmount RETRYAMOUNT] [--timeBetweenRetries TIMEBETWEENRETRIES] [--contextVariables] [--environmentVariables] [--setTag SETTAG] [--delTag] [--getTag] [--renewDeviceId] [--getDeviceId] [--deleteDeviceId] [--setTenantId SETTENANTID] [--getTenantId] [--delTenantId] [--createBlankConfiguration] -Certificate Checker v0.44 +Certificate Checker v0.45 optional arguments: -h, --help show this help message and exit diff --git a/certCheck.py b/certCheck.py index 1781e68..de153ba 100644 --- a/certCheck.py +++ b/certCheck.py @@ -1,7 +1,7 @@ # Program: Certificate Checker # Author: Nolan Rumble -# Date: 2023/07/08 -# Version: 0.44 +# Date: 2023/07/09 +# Version: 0.45 import argparse import datetime @@ -18,7 +18,7 @@ from data import emailTemplateBuilder from data import sendDataEmail -scriptVersion = "0.44" +scriptVersion = "0.45" # Global Variables args = None diff --git a/certificate/getCertificateChain.py b/certificate/getCertificateChain.py index f93e295..375ec66 100644 --- a/certificate/getCertificateChain.py +++ b/certificate/getCertificateChain.py @@ -1,7 +1,7 @@ # Description: Get the certificate chain from a website. # Author: TheScriptGuy -# Last modified: 2023-07-08 -# Version: 0.01 +# Last modified: 2023-07-09 +# Version: 0.02 import ssl import socket @@ -258,7 +258,7 @@ def writeChainToFile(self, __certificateChain: dict) -> None: """Write all the elements in the chain to file.""" myCertChain = __certificateChain - myCertChain.pop() + myCertChain.pop(0) # Iterate through all the elements in the chain. for counter, certificateItem in enumerate(myCertChain): @@ -303,7 +303,7 @@ def getCertificateChain(self, __hostname: str, __port: int): def __init__(self): """Init the getCertChain class.""" - self.classVersion = "0.01" + self.classVersion = "0.02" self.maxDepth = 4 self.certChain = [] self.certificateHash = "" From ee1b636bdf477436554c48d5ba39eb00a865ac42 Mon Sep 17 00:00:00 2001 From: Git Date: Tue, 11 Jul 2023 20:52:59 -0700 Subject: [PATCH 42/44] Version 0.46. See CHANGELOG.md --- CHANGELOG.md | 5 + README.md | 4 +- certCheck.py | 6 +- certificate/certificateModule.py | 189 ++++++++++++++++++++--------- certificate/getCertificateChain.py | 14 +-- data/calculateStats.py | 10 +- data/certData.py | 12 +- 7 files changed, 158 insertions(+), 82 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f727623..ed1fabf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 2023/07/11 +## Version 0.46 +### Enhancements +* Aligning with some python better code practices. + # 2023/07/09 ## Version 0.45 ### Fixes diff --git a/README.md b/README.md index b8166ff..7de810f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Certificate Checker -Version: 0.45 +Version: 0.46 Author: TheScriptGuy @@ -30,7 +30,7 @@ usage: certCheck.py [-h] [--hostname HOSTNAME] [--displayCertificate] [--display [--sendEmail] [--retryAmount RETRYAMOUNT] [--timeBetweenRetries TIMEBETWEENRETRIES] [--contextVariables] [--environmentVariables] [--setTag SETTAG] [--delTag] [--getTag] [--renewDeviceId] [--getDeviceId] [--deleteDeviceId] [--setTenantId SETTENANTID] [--getTenantId] [--delTenantId] [--createBlankConfiguration] -Certificate Checker v0.45 +Certificate Checker v0.46 optional arguments: -h, --help show this help message and exit diff --git a/certCheck.py b/certCheck.py index de153ba..588a545 100644 --- a/certCheck.py +++ b/certCheck.py @@ -1,7 +1,7 @@ # Program: Certificate Checker # Author: Nolan Rumble -# Date: 2023/07/09 -# Version: 0.45 +# Date: 2023/07/11 +# Version: 0.46 import argparse import datetime @@ -18,7 +18,7 @@ from data import emailTemplateBuilder from data import sendDataEmail -scriptVersion = "0.45" +scriptVersion = "0.46" # Global Variables args = None diff --git a/certificate/certificateModule.py b/certificate/certificateModule.py index d9f4f80..8f4d9ec 100644 --- a/certificate/certificateModule.py +++ b/certificate/certificateModule.py @@ -18,7 +18,7 @@ class certificateModule: """certificateModule class""" - def getContextVariables(self): + def getContextVariables(self) -> dict: """Get the variables from the contextVariables.json""" try: # Assume contextVariables is empty. @@ -32,25 +32,32 @@ def getContextVariables(self): except FileNotFoundError: print('I could not find contextVariables.json') - def getCertificate(self, __hostinfo): + def getCertificate(self, __hostinfo: dict) -> dict: """Connect to the host and get the certificate.""" # Determine which context to create - if __hostinfo['options'] is not None and "local_untrusted_allow" in __hostinfo['options']: + if __hostinfo['options'] is not None and \ + "local_untrusted_allow" in __hostinfo['options']: hostnamePortPair = f'{__hostinfo["hostname"]}:{__hostinfo["port"]}' - certificateHashFilename = hashlib.sha256(hostnamePortPair.encode()).hexdigest() + ".pem" + certificateHashFilename = hashlib.sha256( + hostnamePortPair.encode() + ).hexdigest() + ".pem" if not os.path.exists(certificateHashFilename): # Try to build the chain occ = getCertificateChain.getCertificateChain() - occ.getCertificateChain(__hostinfo['hostname'], __hostinfo['port']) + occ.getCertificateChain( + __hostinfo['hostname'], + __hostinfo['port'] + ) __ctx = ssl.create_default_context(cafile=certificateHashFilename) else: # Create the default context. __ctx = ssl.create_default_context() - # Check to see if there are any options that need to be passed for the connection + # Check to see if there are any options that need to be + # passed for the connection if __hostinfo['options'] is not None: if "unsafe_legacy" in __hostinfo['options']: __ctx.options |= 0x4 # OP_LEGACY_SERVER_CONNECT @@ -76,7 +83,10 @@ def getCertificate(self, __hostinfo): with socket.socket() as sock: # Set timeout value for socket to 10 seconds. sock.settimeout(10.0) - with __ctx.wrap_socket(sock, server_hostname=__hostinfo['hostname']) as s: + with __ctx.wrap_socket( + sock, + server_hostname=__hostinfo['hostname'] + ) as s: s.connect((__hostinfo['hostname'], __hostinfo['port'])) __certificate = s.getpeercert() __cipher = s.cipher() @@ -84,29 +94,44 @@ def getCertificate(self, __hostinfo): __hostnameData["connectionCipher"] = __cipher except ssl.SSLCertVerificationError as e: - connectHost = f"{__hostinfo['hostname']}:{__hostinfo['port']}, options: {__hostinfo['options']}" + connectHost = ( + f"{__hostinfo['hostname']}:{__hostinfo['port']}, " + f"options: {__hostinfo['options']}" + ) print(connectHost + ' - Certificate error - ', e.verify_message) except socket.gaierror as e: - connectHost = f"{__hostinfo['hostname']}:{__hostinfo['port']}, options: {__hostinfo['options']}" + connectHost = ( + f"{__hostinfo['hostname']}:{__hostinfo['port']}, " + f"options: {__hostinfo['options']}" + ) print(connectHost + ' - Socket error - ', e.strerror) except FileNotFoundError as e: - connectHost = f"{__hostinfo['hostname']}:{__hostinfo['port']}, options: {__hostinfo['options']}" + connectHost = ( + f"{__hostinfo['hostname']}:{__hostinfo['port']}, " + f"options: {__hostinfo['options']}" + ) print(connectHost + ' - File not found - ', e.strerror) except TimeoutError as e: - connectHost = f"{__hostinfo['hostname']}:{__hostinfo['port']}, options: {__hostinfo['options']}" + connectHost = ( + f"{__hostinfo['hostname']}:{__hostinfo['port']}, " + f"options: {__hostinfo['options']}" + ) print(connectHost + ' - Timeout error - ', e.strerror) except OSError as e: - connectHost = f"{__hostinfo['hostname']}:{__hostinfo['port']}, options: {__hostinfo['options']}" + connectHost = ( + f"{__hostinfo['hostname']}:{__hostinfo['port']}, " + f"options: {__hostinfo['options']}" + ) print(connectHost + ' - OSError - ', e.strerror) return __hostnameData @staticmethod - def printSubject(__certificateObject): + def printSubject(__certificateObject: dict) -> None: """Print the subject name of the certificate.""" if __certificateObject is not None: subject = dict(x[0] for x in __certificateObject['subject']) @@ -114,7 +139,7 @@ def printSubject(__certificateObject): print("Subject: ", issued_to, end='') @staticmethod - def printSubjectAltName(__certificateObject): + def printSubjectAltName(__certificateObject) -> None: """Print the Subject Alternate Name(s) of the certificate.""" __subjectAltName = [] @@ -124,7 +149,7 @@ def printSubjectAltName(__certificateObject): print("Subject Alt Name: ", __subjectAltName) @staticmethod - def printIssuer(__certificateObject): + def printIssuer(__certificateObject) -> None: """Print the Issuer of the certificate.""" if __certificateObject is not None: issuer = dict(x[0] for x in __certificateObject['issuer']) @@ -132,38 +157,43 @@ def printIssuer(__certificateObject): print("Issued by: ", issued_by) @staticmethod - def printNotBefore(__certificateObject): + def printNotBefore(__certificateObject) -> None: """Print the notBefore field of the certificate.""" if __certificateObject is not None: notBefore = __certificateObject['notBefore'] print("Certificate start date: ", notBefore) @staticmethod - def printNotAfter(__certificateObject): + def printNotAfter(__certificateObject) -> None: """Print the notAfter field of the certificate.""" if __certificateObject is not None: notAfter = __certificateObject['notAfter'] print("Certificate end date: ", notAfter) @staticmethod - def returnNotBefore(__certificateObject): + def returnNotBefore(__certificateObject) -> None: """Return the notBefore field from the certificate.""" if __certificateObject is not None: return __certificateObject['notBefore'] return "" @staticmethod - def returnNotAfter(__certificateObject): + def returnNotAfter(__certificateObject) -> None: """Return the notAfter field from the certificate.""" if __certificateObject is not None: return __certificateObject['notAfter'] return "" - def howMuchTimeLeft(self, __certificateObject): + def howMuchTimeLeft(self, __certificateObject) -> None: """Return the remaining time left on the certificate.""" if __certificateObject is not None: timeNow = datetime.datetime.utcnow().replace(microsecond=0) - certNotAfter = datetime.datetime.strptime(self.returnNotAfter(__certificateObject["certificateMetaData"]), self.certTimeFormat) + certNotAfter = datetime.datetime.strptime( + self.returnNotAfter( + __certificateObject["certificateMetaData"] + ), + self.certTimeFormat + ) __delta = relativedelta(certNotAfter, timeNow) @@ -190,16 +220,16 @@ def howMuchTimeLeft(self, __certificateObject): return certResult @staticmethod - def checkIssuer(__certificateObject): + def checkIssuer(__certificateObject) -> bool: """Check to see if issuers are trusted.""" return True @staticmethod - def checkRevocation(__certificateObject): + def checkRevocation(__certificateObject) -> bool: """Check to see if certificate hasn't been revoked.""" return True - def checkTimeValidity(self, __certificateObject): + def checkTimeValidity(self, __certificateObject) -> bool: """ Check to see if the certificate is valid: current date is after certificate start date @@ -207,8 +237,14 @@ def checkTimeValidity(self, __certificateObject): """ if __certificateObject is not None: timeNow = datetime.datetime.utcnow().replace(microsecond=0).date() - certNotAfter = datetime.datetime.strptime(self.returnNotAfter(__certificateObject), self.certTimeFormat).date() - certNotBefore = datetime.datetime.strptime(self.returnNotBefore(__certificateObject), self.certTimeFormat).date() + certNotAfter = datetime.datetime.strptime( + self.returnNotAfter(__certificateObject), + self.certTimeFormat + ).date() + certNotBefore = datetime.datetime.strptime( + self.returnNotBefore(__certificateObject), + self.certTimeFormat + ).date() # Assume time not valid isValid = bool(certNotBefore < timeNow < certNotAfter) @@ -217,7 +253,7 @@ def checkTimeValidity(self, __certificateObject): return False @staticmethod - def printOCSP(__certificateObject): + def printOCSP(__certificateObject) -> None: """Print the OCSP field of the certificate.""" if __certificateObject is not None: __OCSPList = [] @@ -226,7 +262,7 @@ def printOCSP(__certificateObject): print("OCSP: ", __OCSPList) @staticmethod - def printCRLDistributionPoints(__certificateObject): + def printCRLDistributionPoints(__certificateObject) -> None: """Print the CRL distribution points of the certificate.""" if __certificateObject is not None: __CRLList = [] @@ -236,37 +272,39 @@ def printCRLDistributionPoints(__certificateObject): print("CRL: ", __CRLList) @staticmethod - def printCertificateSerialNumber(__certificateObject): + def printCertificateSerialNumber(__certificateObject) -> None: """Print the certificate serial number.""" if __certificateObject is not None: certificateSerialNumber = __certificateObject['serialNumber'] print("Serial Number: ", certificateSerialNumber) @staticmethod - def printCaIssuers(__certificateObject): + def printCaIssuers(__certificateObject) -> None: """Print the certificates CA issuers.""" if __certificateObject is not None: certificateCaIssuers = __certificateObject['caIssuers'] print("CA Issuers: ", certificateCaIssuers) - def printHowMuchTimeLeft(self, __certificateObject): + def printHowMuchTimeLeft(self, __certificateObject) -> None: """Print how much time is left on the certificate.""" if __certificateObject is not None: timeLeft = self.howMuchTimeLeft(__certificateObject) print("Time left: ", timeLeft) - def certificateValid(self, __certificateObject): + def certificateValid(self, __certificateObject) -> None: """ Currently not in use. Check to see if the certificate is valid (Time, Recovation, Issuer) """ if __certificateObject is not None: - if self.checkTimeValidity(__certificateObject) and self.checkRevocation(__certificateObject) and self.checkIssuer(__certificateObject): + if self.checkTimeValidity(__certificateObject) and \ + self.checkRevocation(__certificateObject) and \ + self.checkIssuer(__certificateObject): print("Certificate good!") else: print("Certificate invalid!") - def printCertInfo(self, __certificateObject): + def printCertInfo(self, __certificateObject) -> None: """Print out all the certificate properties.""" if __certificateObject is not None: self.printSubject(__certificateObject) @@ -283,7 +321,7 @@ def printCertInfo(self, __certificateObject): else: print("No certificate info to display!") - def printCertInfoJSON(self, __certificateObject): + def printCertInfoJSON(self, __certificateObject) -> None: """Print the certificate information in JSON format.""" if __certificateObject is not None: jsonCertInfoFormat = json.dumps(__certificateObject) @@ -291,45 +329,63 @@ def printCertInfoJSON(self, __certificateObject): else: jsonCertInfoFormat = { "subject": {"None": "None"}, - "certificateIssuer" : {"None": "None"}, - "version" : 0, - "serialNumber" : "0", - "notBefore" : "Jan 1 00:00:00 0000 GMT", - "notAfter" : "Jan 1 00:00:00 0000 GMT", - "timeLeft" : "0 seconds", - "OCSP" : "None", - "crlDistributionPoints" : "None", - "caIssuers" : "None", - "subjectAltName" : {"None": "None"} + "certificateIssuer": {"None": "None"}, + "version": 0, + "serialNumber": "0", + "notBefore": "Jan 1 00:00:00 0000 GMT", + "notAfter": "Jan 1 00:00:00 0000 GMT", + "timeLeft": "0 seconds", + "OCSP": "None", + "crlDistributionPoints": "None", + "caIssuers": "None", + "subjectAltName": {"None": "None"} } print(jsonCertInfoFormat) - def calculateCertificateUtilization(self, __notBefore, __notAfter): + def calculateCertificateUtilization(self, __notBefore: datetime, __notAfter: datetime) -> float: """Calculating the percentage utilization of the certificate""" # Convert __notBefore to datetime object - notBeforeTime = datetime.datetime.strptime(__notBefore, self.certTimeFormat) + notBeforeTime = datetime.datetime.strptime( + __notBefore, + self.certTimeFormat + ) + # Convert __notAfter to datetime object - notAfterTime = datetime.datetime.strptime(__notAfter, self.certTimeFormat) + notAfterTime = datetime.datetime.strptime( + __notAfter, + self.certTimeFormat + ) # Get the current time. currentTime = datetime.datetime.utcnow() - # Calculate the differences between currentTime, notAfterTime, and notBeforeTime + # Calculate the differences between + # currentTime, notAfterTime, and notBeforeTime rest = notAfterTime - currentTime total = notAfterTime - notBeforeTime - # Calculate the percentage utilization of the time available before expiry. + # Calculate the percentage utilization of the time + # available before expiry. percentageUtilization = 100 - (rest / total * 100) # Return the percentage utilization as a string formatted to 2 places. return float(f"{percentageUtilization:.2f}") - def calculateCertificateTemplateTime(self, __notBefore, __notAfter): - """Calculate the number of seconds between __notBefore and __notAfter.""" + def calculateCertificateTemplateTime(self, __notBefore: datetime, __notAfter: datetime) -> int: + """ + Calculate the number of seconds between + __notBefore and __notAfter. + """ # Convert __notBefore to datetime object - notBeforeTime = datetime.datetime.strptime(__notBefore, self.certTimeFormat) + notBeforeTime = datetime.datetime.strptime( + __notBefore, + self.certTimeFormat + ) # Convert __notAfter to datetime object - notAfterTime = datetime.datetime.strptime(__notAfter, self.certTimeFormat) + notAfterTime = datetime.datetime.strptime( + __notAfter, + self.certTimeFormat + ) # Calcualte the difference timeDifference = notAfterTime - notBeforeTime @@ -339,7 +395,14 @@ def calculateCertificateTemplateTime(self, __notBefore, __notAfter): return timeDifferenceInSeconds - def convertCertificateObject2Json(self, __hostname, __port, __startTime, __endTime, __certificateObject): + def convertCertificateObject2Json( + self, + __hostname: str, + __port: int, + __startTime: datetime, + __endTime: datetime, + __certificateObject: dict + ) -> dict: """Convert the certificate object into JSON format.""" myJsonCertificateInfo = {} @@ -347,7 +410,12 @@ def convertCertificateObject2Json(self, __hostname, __port, __startTime, __endTi endTime = __endTime.isoformat() # Calculate queryTime between __endTime and __startTime in milliseconds - queryTime = round(float((__endTime - __startTime).total_seconds() * 1000), 2) + queryTime = round( + float( + ( + __endTime - __startTime + ).total_seconds() * 1000 + ), 2) myJsonCertificateInfo["hostname"] = __hostname myJsonCertificateInfo["port"] = int(__port) @@ -356,7 +424,8 @@ def convertCertificateObject2Json(self, __hostname, __port, __startTime, __endTi myJsonCertificateInfo["queryTime"] = queryTime if __certificateObject["connectionCipher"] is not None: - myJsonCertificateInfo["connectionCipher"] = __certificateObject["connectionCipher"] + myJsonCertificateInfo["connectionCipher"] = \ + __certificateObject["connectionCipher"] myJsonCertificateInfo["certificateInfo"] = {} @@ -366,7 +435,9 @@ def convertCertificateObject2Json(self, __hostname, __port, __startTime, __endTi # Certificate might not have subject defined. if 'subject' in certKeys: - myJsonCertificateInfo["certificateInfo"]["subject"] = dict(x[0] for x in __certificateObject["certificateMetaData"]["subject"]) + myJsonCertificateInfo["certificateInfo"]["subject"] = dict( + x[0] for x in __certificateObject["certificateMetaData"]["subject"] + ) myJsonCertificateInfo["certificateInfo"]["certificateIssuer"] = dict(x[0] for x in __certificateObject["certificateMetaData"]["issuer"]) @@ -429,7 +500,7 @@ def convertCertificateObject2Json(self, __hostname, __port, __startTime, __endTi return myJsonCertificateInfo - def uploadJsonData(self, __certificateJsonData, __httpUrl): + def uploadJsonData(self, __certificateJsonData: dict, __httpUrl: str) -> str: """ This will upload the json data to a URL via a POST method. If the verbose argument is set, it'll display what URL it's being diff --git a/certificate/getCertificateChain.py b/certificate/getCertificateChain.py index 375ec66..93991ce 100644 --- a/certificate/getCertificateChain.py +++ b/certificate/getCertificateChain.py @@ -1,7 +1,7 @@ # Description: Get the certificate chain from a website. # Author: TheScriptGuy -# Last modified: 2023-07-09 -# Version: 0.02 +# Last modified: 2023-07-11 +# Version: 0.03 import ssl import socket @@ -78,7 +78,7 @@ def loadRootCACertChain(__filename: str) -> dict: sys.exit(1) @staticmethod - def getCertificate(__hostname: str, __port: int): + def getCertificate(__hostname: str, __port: int) -> x509.Certificate: """Retrieves the certificate from the website.""" try: """ @@ -103,7 +103,7 @@ def getCertificate(__hostname: str, __port: int): return sslCertificate @staticmethod - def getCertificateFromUri(__uri: str): + def getCertificateFromUri(__uri: str) -> str: """Gets the certificate from a URI. By default, we're expecting to find nothing. Therefore certI = None. If we find something, we'll update certI accordingly. @@ -128,7 +128,7 @@ def getCertificateFromUri(__uri: str): return certI @staticmethod - def returnCertAKI(__sslCertificate): + def returnCertAKI(__sslCertificate: x509.Certificate) -> x509.extensions.Extension: """Returns the AKI of the certificate.""" try: certAKI = __sslCertificate.extensions.get_extension_for_oid(ExtensionOID.AUTHORITY_KEY_IDENTIFIER) @@ -155,7 +155,7 @@ def returnCertAIA(__sslCertificate): return certAIA @staticmethod - def returnCertAIAList(__sslCertificate): + def returnCertAIAList(__sslCertificate: x509.Certificate) -> list: """Returns a list of AIA's defined in __sslCertificate.""" aiaUriList = [] @@ -173,7 +173,7 @@ def returnCertAIAList(__sslCertificate): # Return the aiaUriList back to the script. return aiaUriList - def walkTheChain(self, __sslCertificate, __depth: int): + def walkTheChain(self, __sslCertificate: x509.Certificate, __depth: int): """ Walk the length of the chain, fetching information from AIA along the way until AKI == SKI (i.e. we've found the Root CA. diff --git a/data/calculateStats.py b/data/calculateStats.py index 5cfd7e4..4ff45e6 100644 --- a/data/calculateStats.py +++ b/data/calculateStats.py @@ -1,7 +1,7 @@ # Class: calculateStats # Author: Nolan Rumble -# Date: 2022/12/18 -# Version: 0.05 +# Date: 2023/07/11 +# Version: 0.06 import datetime @@ -12,7 +12,7 @@ class calculateStats: """This calculates statistics off the data provided.""" @staticmethod - def convertTimeIntoHumanReadable(__seconds): + def convertTimeIntoHumanReadable(__seconds: int) -> str: """Return the remaining time left on the certificate.""" # Get date/time since epoch based off seconds myDateTime = datetime.datetime.fromtimestamp(__seconds) @@ -49,7 +49,7 @@ def convertTimeIntoHumanReadable(__seconds): # Return the human readable form string. return myDateTimeString - def calculateStatistics(self, __certResults): + def calculateStatistics(self, __certResults: dict) -> dict: """Returns statistics based off certificate information provided.""" # Calculate the average utilization and query time across all tests. avgUtilization = float(0) @@ -160,7 +160,7 @@ def calculateStatistics(self, __certResults): return combinedStatistics - def combineData(self, __certResults, __mySystemInfo, __scriptStartTime, __scriptEndTime): + def combineData(self, __certResults: dict, __mySystemInfo: dict, __scriptStartTime: datetime, __scriptEndTime: datetime) -> dict: """Combines all the data into structured data.""" # Convert script start/end times into string isoformat scriptStartTime = __scriptStartTime.isoformat() diff --git a/data/certData.py b/data/certData.py index f18b1e2..861db9f 100644 --- a/data/certData.py +++ b/data/certData.py @@ -1,7 +1,7 @@ # Description: Certificate Data Handling # Author: TheScriptGuy -# Version: 0.08 -# Last modified: 2023/05/31 +# Version: 0.09 +# Last modified: 2023/07/11 import sys from os import path @@ -14,7 +14,7 @@ class certData: """certData class""" @staticmethod - def getFileFromURL(fileURL): + def getFileFromURL(fileURL: str) -> list: """This function will download the contents of fileURL and return a list with the contents.""" tmpData = [] try: @@ -56,7 +56,7 @@ def uploadJsonHTTP(url, jsonData): return x.headers @staticmethod - def parse_line(line): + def parse_line(line: str) -> dict: # Default values hostname = '' port = 443 @@ -81,7 +81,7 @@ def parse_line(line): return {"hostname": hostname, "port": port, "options": options} @staticmethod - def loadQueriesFile(queriesFile): + def loadQueriesFile(queriesFile: str) -> list: """ This will load the queries that need to be performed against each name server. One hostname entry per line. @@ -109,4 +109,4 @@ def loadQueriesFile(queriesFile): def __init__(self): """Initialize the certData class.""" self.initialized = True - self.version = "0.08" + self.version = "0.09" From 0b57489a638b2a88f9d93fca6ff0b6724f0c8ffe Mon Sep 17 00:00:00 2001 From: Git Date: Sat, 29 Jul 2023 14:49:47 -0700 Subject: [PATCH 43/44] Aligning to PEP standards --- certificate/certificateModule.py | 111 +++++++++++++++-------------- certificate/getCertificateChain.py | 29 ++++---- 2 files changed, 71 insertions(+), 69 deletions(-) diff --git a/certificate/certificateModule.py b/certificate/certificateModule.py index 8f4d9ec..e396f3b 100644 --- a/certificate/certificateModule.py +++ b/certificate/certificateModule.py @@ -40,16 +40,16 @@ def getCertificate(self, __hostinfo: dict) -> dict: "local_untrusted_allow" in __hostinfo['options']: hostnamePortPair = f'{__hostinfo["hostname"]}:{__hostinfo["port"]}' certificateHashFilename = hashlib.sha256( - hostnamePortPair.encode() - ).hexdigest() + ".pem" + hostnamePortPair.encode() + ).hexdigest() + ".pem" if not os.path.exists(certificateHashFilename): # Try to build the chain occ = getCertificateChain.getCertificateChain() occ.getCertificateChain( - __hostinfo['hostname'], - __hostinfo['port'] - ) + __hostinfo['hostname'], + __hostinfo['port'] + ) __ctx = ssl.create_default_context(cafile=certificateHashFilename) else: @@ -84,9 +84,9 @@ def getCertificate(self, __hostinfo: dict) -> dict: # Set timeout value for socket to 10 seconds. sock.settimeout(10.0) with __ctx.wrap_socket( - sock, - server_hostname=__hostinfo['hostname'] - ) as s: + sock, + server_hostname=__hostinfo['hostname'] + ) as s: s.connect((__hostinfo['hostname'], __hostinfo['port'])) __certificate = s.getpeercert() __cipher = s.cipher() @@ -95,37 +95,37 @@ def getCertificate(self, __hostinfo: dict) -> dict: except ssl.SSLCertVerificationError as e: connectHost = ( - f"{__hostinfo['hostname']}:{__hostinfo['port']}, " - f"options: {__hostinfo['options']}" - ) + f"{__hostinfo['hostname']}:{__hostinfo['port']}, " + f"options: {__hostinfo['options']}" + ) print(connectHost + ' - Certificate error - ', e.verify_message) except socket.gaierror as e: connectHost = ( - f"{__hostinfo['hostname']}:{__hostinfo['port']}, " - f"options: {__hostinfo['options']}" - ) + f"{__hostinfo['hostname']}:{__hostinfo['port']}, " + f"options: {__hostinfo['options']}" + ) print(connectHost + ' - Socket error - ', e.strerror) except FileNotFoundError as e: connectHost = ( - f"{__hostinfo['hostname']}:{__hostinfo['port']}, " - f"options: {__hostinfo['options']}" - ) + f"{__hostinfo['hostname']}:{__hostinfo['port']}, " + f"options: {__hostinfo['options']}" + ) print(connectHost + ' - File not found - ', e.strerror) except TimeoutError as e: connectHost = ( - f"{__hostinfo['hostname']}:{__hostinfo['port']}, " - f"options: {__hostinfo['options']}" - ) + f"{__hostinfo['hostname']}:{__hostinfo['port']}, " + f"options: {__hostinfo['options']}" + ) print(connectHost + ' - Timeout error - ', e.strerror) except OSError as e: connectHost = ( - f"{__hostinfo['hostname']}:{__hostinfo['port']}, " - f"options: {__hostinfo['options']}" - ) + f"{__hostinfo['hostname']}:{__hostinfo['port']}, " + f"options: {__hostinfo['options']}" + ) print(connectHost + ' - OSError - ', e.strerror) return __hostnameData @@ -189,11 +189,11 @@ def howMuchTimeLeft(self, __certificateObject) -> None: if __certificateObject is not None: timeNow = datetime.datetime.utcnow().replace(microsecond=0) certNotAfter = datetime.datetime.strptime( - self.returnNotAfter( - __certificateObject["certificateMetaData"] - ), - self.certTimeFormat - ) + self.returnNotAfter( + __certificateObject["certificateMetaData"] + ), + self.certTimeFormat + ) __delta = relativedelta(certNotAfter, timeNow) @@ -238,13 +238,14 @@ def checkTimeValidity(self, __certificateObject) -> bool: if __certificateObject is not None: timeNow = datetime.datetime.utcnow().replace(microsecond=0).date() certNotAfter = datetime.datetime.strptime( - self.returnNotAfter(__certificateObject), - self.certTimeFormat - ).date() + self.returnNotAfter(__certificateObject), + self.certTimeFormat + ).date() + certNotBefore = datetime.datetime.strptime( - self.returnNotBefore(__certificateObject), - self.certTimeFormat - ).date() + self.returnNotBefore(__certificateObject), + self.certTimeFormat + ).date() # Assume time not valid isValid = bool(certNotBefore < timeNow < certNotAfter) @@ -346,15 +347,15 @@ def calculateCertificateUtilization(self, __notBefore: datetime, __notAfter: dat """Calculating the percentage utilization of the certificate""" # Convert __notBefore to datetime object notBeforeTime = datetime.datetime.strptime( - __notBefore, - self.certTimeFormat - ) + __notBefore, + self.certTimeFormat + ) # Convert __notAfter to datetime object notAfterTime = datetime.datetime.strptime( - __notAfter, - self.certTimeFormat - ) + __notAfter, + self.certTimeFormat + ) # Get the current time. currentTime = datetime.datetime.utcnow() @@ -378,14 +379,15 @@ def calculateCertificateTemplateTime(self, __notBefore: datetime, __notAfter: da """ # Convert __notBefore to datetime object notBeforeTime = datetime.datetime.strptime( - __notBefore, - self.certTimeFormat - ) + __notBefore, + self.certTimeFormat + ) + # Convert __notAfter to datetime object notAfterTime = datetime.datetime.strptime( - __notAfter, - self.certTimeFormat - ) + __notAfter, + self.certTimeFormat + ) # Calcualte the difference timeDifference = notAfterTime - notBeforeTime @@ -411,11 +413,12 @@ def convertCertificateObject2Json( # Calculate queryTime between __endTime and __startTime in milliseconds queryTime = round( - float( - ( - __endTime - __startTime - ).total_seconds() * 1000 - ), 2) + float( + ( + __endTime - __startTime + ).total_seconds() * 1000 + ), 2 + ) myJsonCertificateInfo["hostname"] = __hostname myJsonCertificateInfo["port"] = int(__port) @@ -425,7 +428,7 @@ def convertCertificateObject2Json( if __certificateObject["connectionCipher"] is not None: myJsonCertificateInfo["connectionCipher"] = \ - __certificateObject["connectionCipher"] + __certificateObject["connectionCipher"] myJsonCertificateInfo["certificateInfo"] = {} @@ -436,8 +439,8 @@ def convertCertificateObject2Json( # Certificate might not have subject defined. if 'subject' in certKeys: myJsonCertificateInfo["certificateInfo"]["subject"] = dict( - x[0] for x in __certificateObject["certificateMetaData"]["subject"] - ) + x[0] for x in __certificateObject["certificateMetaData"]["subject"] + ) myJsonCertificateInfo["certificateInfo"]["certificateIssuer"] = dict(x[0] for x in __certificateObject["certificateMetaData"]["issuer"]) diff --git a/certificate/getCertificateChain.py b/certificate/getCertificateChain.py index 93991ce..aea4cb3 100644 --- a/certificate/getCertificateChain.py +++ b/certificate/getCertificateChain.py @@ -1,7 +1,7 @@ # Description: Get the certificate chain from a website. # Author: TheScriptGuy -# Last modified: 2023-07-11 -# Version: 0.03 +# Last modified: 2023-07-29 +# Version: 0.04 import ssl import socket @@ -23,7 +23,7 @@ class getCertificateChain: In some rare occassions where a website is poorly configured. e.g. not presenting the full certificate chain, the python's ssl fails to connect. This class will attempt to collect the certificate chain and store it to file for - later use. + later use. """ @staticmethod def loadRootCACertChain(__filename: str) -> dict: @@ -105,14 +105,14 @@ def getCertificate(__hostname: str, __port: int) -> x509.Certificate: @staticmethod def getCertificateFromUri(__uri: str) -> str: """Gets the certificate from a URI. - By default, we're expecting to find nothing. Therefore certI = None. + By default, we're expecting to find nothing. Therefore certI = None. If we find something, we'll update certI accordingly. """ certI = None # Attempt to get the aia from __uri aiaRequest = requests.get(__uri) - + # If response status code is 200 if aiaRequest.status_code == 200: # Get the content and assign to aiaContent @@ -151,7 +151,7 @@ def returnCertAIA(__sslCertificate): except x509.extensions.ExtensionNotFound: certAIA = None - + return certAIA @staticmethod @@ -175,10 +175,10 @@ def returnCertAIAList(__sslCertificate: x509.Certificate) -> list: def walkTheChain(self, __sslCertificate: x509.Certificate, __depth: int): """ - Walk the length of the chain, fetching information from AIA + Walk the length of the chain, fetching information from AIA along the way until AKI == SKI (i.e. we've found the Root CA. - This is to prevent recursive loops. Usually there are only 4 certificates. + This is to prevent recursive loops. Usually there are only 4 certificates. If the maxDepth is too small (why?) adjust it at the beginning of the script. """ @@ -196,7 +196,7 @@ def walkTheChain(self, __sslCertificate: x509.Certificate, __depth: int): # Get the value of the SKI from certSKI certSKIValue = certSKI._value.digest - + # Sometimes the AKI can be none. Lets handle this accordingly. if certAKIValue is not None: aiaUriList = self.returnCertAIAList(__sslCertificate) @@ -230,7 +230,7 @@ def walkTheChain(self, __sslCertificate: x509.Certificate, __depth: int): rootCACertificatePEM = caRootStore[rootCA] rootCACertificate = x509.load_pem_x509_certificate(rootCACertificatePEM.encode('ascii')) rootCASKI = self.returnCertSKI(rootCACertificate) - rootCASKI_Value = rootCASKI._value.digest + rootCASKI_Value = rootCASKI._value.digest if rootCASKI_Value == certAKIValue: rootCACN = rootCA print(f"Root CA Found - {rootCACN}") @@ -240,7 +240,7 @@ def walkTheChain(self, __sslCertificate: x509.Certificate, __depth: int): # Apparently some Root CA's don't have a SKI? pass - if rootCACN == None: + if rootCACN is None: print("ERROR - Root CA NOT found.") sys.exit(1) @@ -281,7 +281,7 @@ def getCertificateChain(self, __hostname: str, __port: int): # Get the website certificate object from myHostname["hostname"]:myHostname["port"] __websiteCertificate = self.getCertificate(__hostname, __port) - + if __websiteCertificate is not None: # Get the AIA from the __websiteCertificate object aia = self.returnCertAIA(__websiteCertificate) @@ -293,7 +293,7 @@ def getCertificateChain(self, __hostname: str, __port: int): self.certChain.append(__websiteCertificate) # Now we walk the chain up until we get the Root CA. - self.walkTheChain(__websiteCertificate,1) + self.walkTheChain(__websiteCertificate, 1) # Write the certificate chain to individual files. self.writeChainToFile(self.certChain) @@ -303,8 +303,7 @@ def getCertificateChain(self, __hostname: str, __port: int): def __init__(self): """Init the getCertChain class.""" - self.classVersion = "0.02" + self.classVersion = "0.04" self.maxDepth = 4 self.certChain = [] self.certificateHash = "" - From 7c994376cbc216b89c881a6bee74bffcfa1ceea9 Mon Sep 17 00:00:00 2001 From: Sourcery AI <> Date: Sun, 30 Jul 2023 05:23:00 +0000 Subject: [PATCH 44/44] 'Refactored by Sourcery' --- certCheck.py | 21 ++--- certificate/certificateModule.py | 128 ++++++++++++---------------- certificate/getCertificateChain.py | 132 ++++++++++++++--------------- data/calculateStats.py | 61 +++++++------ data/certData.py | 12 ++- data/emailTemplateBuilder.py | 14 +-- data/sendDataMongoDB.py | 28 +++--- systemInfo/systemInfo.py | 31 ++----- 8 files changed, 194 insertions(+), 233 deletions(-) diff --git a/certCheck.py b/certCheck.py index 588a545..b2879a7 100644 --- a/certCheck.py +++ b/certCheck.py @@ -30,7 +30,9 @@ def parseArguments(): """Create argument options and parse through them to determine what to do with script.""" # Instantiate the parser - parser = argparse.ArgumentParser(description='Certificate Checker v' + scriptVersion) + parser = argparse.ArgumentParser( + description=f'Certificate Checker v{scriptVersion}' + ) # Optional arguments parser.add_argument('--hostname', default='', @@ -194,10 +196,9 @@ def gatherData(__certResults, __mySystemInfo, __scriptStartTime, __scriptEndTime """ myDetails = calculateStats.calculateStats() - # Create the json script structure with all the meta data. - myData = myDetails.combineData(__certResults, __mySystemInfo, __scriptStartTime, __scriptEndTime) - - return myData + return myDetails.combineData( + __certResults, __mySystemInfo, __scriptStartTime, __scriptEndTime + ) def checkArguments(__myCertificate, __jsonCertificateInfo): @@ -307,7 +308,7 @@ def processQueryFile(): sdMDB = sendDataMongoDB.sendDataMongoDB() uploadResult = sdMDB.uploadDataToMongoDB(myJsonScriptData) uploadTime = str(datetime.datetime.utcnow()) - print(uploadTime + " - " + str(uploadResult)) + print(f"{uploadTime} - {str(uploadResult)}") if args.sendEmail: # Send an email with the results. @@ -318,11 +319,7 @@ def processHostname(): """This will attempt to connect to the hostname defined by the --hostname argument.""" # Define initial certificate object - if args.contextVariables: - contextVariables = 1 - else: - contextVariables = 0 - + contextVariables = 1 if args.contextVariables else 0 o_myCertificate = certificateModule.certificateModule(contextVariables) o_myCertData = certData.certData() @@ -376,7 +373,7 @@ def processHostname(): sdMDB = sendDataMongoDB.sendDataMongoDB() uploadResult = sdMDB.uploadDataToMongoDB(jsonScriptData) uploadTime = str(datetime.datetime.utcnow()) - print(uploadTime + " - " + str(uploadResult)) + print(f"{uploadTime} - {str(uploadResult)}") if args.sendEmail: # Send an email with the results. diff --git a/certificate/certificateModule.py b/certificate/certificateModule.py index e396f3b..001638c 100644 --- a/certificate/certificateModule.py +++ b/certificate/certificateModule.py @@ -37,7 +37,7 @@ def getCertificate(self, __hostinfo: dict) -> dict: # Determine which context to create if __hostinfo['options'] is not None and \ - "local_untrusted_allow" in __hostinfo['options']: + "local_untrusted_allow" in __hostinfo['options']: hostnamePortPair = f'{__hostinfo["hostname"]}:{__hostinfo["port"]}' certificateHashFilename = hashlib.sha256( hostnamePortPair.encode() @@ -98,35 +98,35 @@ def getCertificate(self, __hostinfo: dict) -> dict: f"{__hostinfo['hostname']}:{__hostinfo['port']}, " f"options: {__hostinfo['options']}" ) - print(connectHost + ' - Certificate error - ', e.verify_message) + print(f'{connectHost} - Certificate error - ', e.verify_message) except socket.gaierror as e: connectHost = ( f"{__hostinfo['hostname']}:{__hostinfo['port']}, " f"options: {__hostinfo['options']}" ) - print(connectHost + ' - Socket error - ', e.strerror) + print(f'{connectHost} - Socket error - ', e.strerror) except FileNotFoundError as e: connectHost = ( f"{__hostinfo['hostname']}:{__hostinfo['port']}, " f"options: {__hostinfo['options']}" ) - print(connectHost + ' - File not found - ', e.strerror) + print(f'{connectHost} - File not found - ', e.strerror) except TimeoutError as e: connectHost = ( f"{__hostinfo['hostname']}:{__hostinfo['port']}, " f"options: {__hostinfo['options']}" ) - print(connectHost + ' - Timeout error - ', e.strerror) + print(f'{connectHost} - Timeout error - ', e.strerror) except OSError as e: connectHost = ( f"{__hostinfo['hostname']}:{__hostinfo['port']}, " f"options: {__hostinfo['options']}" ) - print(connectHost + ' - OSError - ', e.strerror) + print(f'{connectHost} - OSError - ', e.strerror) return __hostnameData @@ -141,11 +141,10 @@ def printSubject(__certificateObject: dict) -> None: @staticmethod def printSubjectAltName(__certificateObject) -> None: """Print the Subject Alternate Name(s) of the certificate.""" - __subjectAltName = [] - - for field, value in __certificateObject['subjectAltName']: - __subjectAltName.append({field: value}) - + __subjectAltName = [ + {field: value} + for field, value in __certificateObject['subjectAltName'] + ] print("Subject Alt Name: ", __subjectAltName) @staticmethod @@ -173,51 +172,44 @@ def printNotAfter(__certificateObject) -> None: @staticmethod def returnNotBefore(__certificateObject) -> None: """Return the notBefore field from the certificate.""" - if __certificateObject is not None: - return __certificateObject['notBefore'] - return "" + return "" if __certificateObject is None else __certificateObject['notBefore'] @staticmethod def returnNotAfter(__certificateObject) -> None: """Return the notAfter field from the certificate.""" - if __certificateObject is not None: - return __certificateObject['notAfter'] - return "" + return "" if __certificateObject is None else __certificateObject['notAfter'] def howMuchTimeLeft(self, __certificateObject) -> None: """Return the remaining time left on the certificate.""" - if __certificateObject is not None: - timeNow = datetime.datetime.utcnow().replace(microsecond=0) - certNotAfter = datetime.datetime.strptime( - self.returnNotAfter( - __certificateObject["certificateMetaData"] - ), - self.certTimeFormat - ) + if __certificateObject is None: + return "Invalid certificate" + timeNow = datetime.datetime.utcnow().replace(microsecond=0) + certNotAfter = datetime.datetime.strptime( + self.returnNotAfter( + __certificateObject["certificateMetaData"] + ), + self.certTimeFormat + ) - __delta = relativedelta(certNotAfter, timeNow) + __delta = relativedelta(certNotAfter, timeNow) - myDeltaDate = { - 'years': __delta.years, - 'months': __delta.months, - 'days': __delta.days, - 'hours': __delta.hours, - 'minutes': __delta.minutes, - 'seconds': __delta.seconds, - } - timeLeft = [] + myDeltaDate = { + 'years': __delta.years, + 'months': __delta.months, + 'days': __delta.days, + 'hours': __delta.hours, + 'minutes': __delta.minutes, + 'seconds': __delta.seconds, + } + timeLeft = [] - for field in myDeltaDate: - if myDeltaDate[field] > 1: - timeLeft.append(f"{myDeltaDate[field]} {field}") - else: - if myDeltaDate[field] == 1: - timeLeft.append(f"{myDeltaDate[field]} {field[:-1]}") + for field, value in myDeltaDate.items(): + if value > 1: + timeLeft.append(f"{myDeltaDate[field]} {field}") + elif myDeltaDate[field] == 1: + timeLeft.append(f"{myDeltaDate[field]} {field[:-1]}") - certResult = ', '.join(timeLeft) - else: - certResult = "Invalid certificate" - return certResult + return ', '.join(timeLeft) @staticmethod def checkIssuer(__certificateObject) -> bool: @@ -247,29 +239,22 @@ def checkTimeValidity(self, __certificateObject) -> bool: self.certTimeFormat ).date() - # Assume time not valid - isValid = bool(certNotBefore < timeNow < certNotAfter) - - return isValid + return certNotBefore < timeNow < certNotAfter return False @staticmethod def printOCSP(__certificateObject) -> None: """Print the OCSP field of the certificate.""" if __certificateObject is not None: - __OCSPList = [] - for value in __certificateObject['OCSP']: - __OCSPList.append(value) + __OCSPList = list(__certificateObject['OCSP']) print("OCSP: ", __OCSPList) @staticmethod def printCRLDistributionPoints(__certificateObject) -> None: """Print the CRL distribution points of the certificate.""" if __certificateObject is not None: - __CRLList = [] if 'crlDistributionPoints' in __certificateObject: - for value in __certificateObject['crlDistributionPoints']: - __CRLList.append(value) + __CRLList = list(__certificateObject['crlDistributionPoints']) print("CRL: ", __CRLList) @staticmethod @@ -326,7 +311,6 @@ def printCertInfoJSON(self, __certificateObject) -> None: """Print the certificate information in JSON format.""" if __certificateObject is not None: jsonCertInfoFormat = json.dumps(__certificateObject) - print(jsonCertInfoFormat) else: jsonCertInfoFormat = { "subject": {"None": "None"}, @@ -341,7 +325,8 @@ def printCertInfoJSON(self, __certificateObject) -> None: "caIssuers": "None", "subjectAltName": {"None": "None"} } - print(jsonCertInfoFormat) + + print(jsonCertInfoFormat) def calculateCertificateUtilization(self, __notBefore: datetime, __notAfter: datetime) -> float: """Calculating the percentage utilization of the certificate""" @@ -392,10 +377,7 @@ def calculateCertificateTemplateTime(self, __notBefore: datetime, __notAfter: da # Calcualte the difference timeDifference = notAfterTime - notBeforeTime - # Get the number of seconds from the calculation above - timeDifferenceInSeconds = int(timeDifference.total_seconds()) - - return timeDifferenceInSeconds + return int(timeDifference.total_seconds()) def convertCertificateObject2Json( self, @@ -406,8 +388,6 @@ def convertCertificateObject2Json( __certificateObject: dict ) -> dict: """Convert the certificate object into JSON format.""" - myJsonCertificateInfo = {} - startTime = __startTime.isoformat() endTime = __endTime.isoformat() @@ -420,15 +400,16 @@ def convertCertificateObject2Json( ), 2 ) - myJsonCertificateInfo["hostname"] = __hostname - myJsonCertificateInfo["port"] = int(__port) - myJsonCertificateInfo["startTime"] = startTime - myJsonCertificateInfo["endTime"] = endTime - myJsonCertificateInfo["queryTime"] = queryTime - + myJsonCertificateInfo = { + "hostname": __hostname, + "port": __port, + "startTime": startTime, + "endTime": endTime, + "queryTime": queryTime, + } if __certificateObject["connectionCipher"] is not None: myJsonCertificateInfo["connectionCipher"] = \ - __certificateObject["connectionCipher"] + __certificateObject["connectionCipher"] myJsonCertificateInfo["certificateInfo"] = {} @@ -461,13 +442,8 @@ def convertCertificateObject2Json( # Initialize subjectAltName myJsonCertificateInfo["certificateInfo"]["subjectAltName"] = {} - # Keep track of how many entries there are - subjectAltNameCounter = 0 - - for field, value in __certificateObject["certificateMetaData"]["subjectAltName"]: + for subjectAltNameCounter, (field, value) in enumerate(__certificateObject["certificateMetaData"]["subjectAltName"]): myJsonCertificateInfo["certificateInfo"]["subjectAltName"].update({field + str(subjectAltNameCounter): value}) - subjectAltNameCounter += 1 - # Time left on certificate myJsonCertificateInfo["timeLeft"] = self.howMuchTimeLeft(__certificateObject) diff --git a/certificate/getCertificateChain.py b/certificate/getCertificateChain.py index aea4cb3..05382fb 100644 --- a/certificate/getCertificateChain.py +++ b/certificate/getCertificateChain.py @@ -139,9 +139,9 @@ def returnCertAKI(__sslCertificate: x509.Certificate) -> x509.extensions.Extensi @staticmethod def returnCertSKI(__sslCertificate): """Returns the SKI of the certificate.""" - certSKI = __sslCertificate.extensions.get_extension_for_oid(ExtensionOID.SUBJECT_KEY_IDENTIFIER) - - return certSKI + return __sslCertificate.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_KEY_IDENTIFIER + ) @staticmethod def returnCertAIA(__sslCertificate): @@ -165,11 +165,12 @@ def returnCertAIAList(__sslCertificate: x509.Certificate) -> list: # If the extension is x509.AuthorityInformationAccess) then lets get the caIssuers from the field. if isinstance(certValue, x509.AuthorityInformationAccess): - dataAIA = [x for x in certValue or []] - for item in dataAIA: - if item.access_method._name == "caIssuers": - aiaUriList.append(item.access_location._value) - + dataAIA = list(certValue or []) + aiaUriList.extend( + item.access_location._value + for item in dataAIA + if item.access_method._name == "caIssuers" + ) # Return the aiaUriList back to the script. return aiaUriList @@ -182,67 +183,64 @@ def walkTheChain(self, __sslCertificate: x509.Certificate, __depth: int): If the maxDepth is too small (why?) adjust it at the beginning of the script. """ - if __depth <= self.maxDepth: - # Retrive the AKI from the certificate. - certAKI = self.returnCertAKI(__sslCertificate) - # Retrieve the SKI from the certificate. - certSKI = self.returnCertSKI(__sslCertificate) + if __depth > self.maxDepth: + return + # Retrive the AKI from the certificate. + certAKI = self.returnCertAKI(__sslCertificate) + # Retrieve the SKI from the certificate. + certSKI = self.returnCertSKI(__sslCertificate) # Sometimes the AKI can be none. Lets handle this accordingly. - if certAKI is not None: - certAKIValue = certAKI._value.key_identifier - else: - certAKIValue = None - - # Get the value of the SKI from certSKI - certSKIValue = certSKI._value.digest - - # Sometimes the AKI can be none. Lets handle this accordingly. - if certAKIValue is not None: - aiaUriList = self.returnCertAIAList(__sslCertificate) - if aiaUriList != []: - # Iterate through the aiaUriList list. - for item in aiaUriList: - # get the certificate for the item element. - nextCert = self.getCertificateFromUri(item) - - # If the certificate is not none (great), append it to the certChain, increase the __depth and run the walkTheChain subroutine again. - if nextCert is not None: - self.certChain.append(nextCert) - __depth += 1 - self.walkTheChain(nextCert, __depth) - else: - print("Could not retrieve certificate.") - sys.exit(1) - else: - """Now we have to go on a hunt to find the root from a standard root store.""" - print("Certificate didn't have AIA...ruh roh.") - - # Load the Root CA Cert Chain. - caRootStore = self.loadRootCACertChain("cacert.pem") - - # Assume we cannot find a Root CA - rootCACN = None - - # Iterate through the caRootStore object. - for rootCA in caRootStore: - try: - rootCACertificatePEM = caRootStore[rootCA] - rootCACertificate = x509.load_pem_x509_certificate(rootCACertificatePEM.encode('ascii')) - rootCASKI = self.returnCertSKI(rootCACertificate) - rootCASKI_Value = rootCASKI._value.digest - if rootCASKI_Value == certAKIValue: - rootCACN = rootCA - print(f"Root CA Found - {rootCACN}") - self.certChain.append(rootCACertificate) - break - except x509.extensions.ExtensionNotFound: - # Apparently some Root CA's don't have a SKI? - pass - - if rootCACN is None: - print("ERROR - Root CA NOT found.") + certAKIValue = certAKI._value.key_identifier if certAKI is not None else None + # Get the value of the SKI from certSKI + certSKIValue = certSKI._value.digest + + # Sometimes the AKI can be none. Lets handle this accordingly. + if certAKIValue is not None: + aiaUriList = self.returnCertAIAList(__sslCertificate) + if aiaUriList != []: + # Iterate through the aiaUriList list. + for item in aiaUriList: + # get the certificate for the item element. + nextCert = self.getCertificateFromUri(item) + + # If the certificate is not none (great), append it to the certChain, increase the __depth and run the walkTheChain subroutine again. + if nextCert is not None: + self.certChain.append(nextCert) + __depth += 1 + self.walkTheChain(nextCert, __depth) + else: + print("Could not retrieve certificate.") sys.exit(1) + else: + """Now we have to go on a hunt to find the root from a standard root store.""" + print("Certificate didn't have AIA...ruh roh.") + + # Load the Root CA Cert Chain. + caRootStore = self.loadRootCACertChain("cacert.pem") + + # Assume we cannot find a Root CA + rootCACN = None + + # Iterate through the caRootStore object. + for rootCA in caRootStore: + try: + rootCACertificatePEM = caRootStore[rootCA] + rootCACertificate = x509.load_pem_x509_certificate(rootCACertificatePEM.encode('ascii')) + rootCASKI = self.returnCertSKI(rootCACertificate) + rootCASKI_Value = rootCASKI._value.digest + if rootCASKI_Value == certAKIValue: + rootCACN = rootCA + print(f"Root CA Found - {rootCACN}") + self.certChain.append(rootCACertificate) + break + except x509.extensions.ExtensionNotFound: + # Apparently some Root CA's don't have a SKI? + pass + + if rootCACN is None: + print("ERROR - Root CA NOT found.") + sys.exit(1) @staticmethod def sendCertificateToFile(__filename: str, __sslCertificate) -> None: @@ -261,7 +259,7 @@ def writeChainToFile(self, __certificateChain: dict) -> None: myCertChain.pop(0) # Iterate through all the elements in the chain. - for counter, certificateItem in enumerate(myCertChain): + for certificateItem in myCertChain: # Get the subject from the certificate. certSubject = certificateItem.subject.rfc4514_string() diff --git a/data/calculateStats.py b/data/calculateStats.py index 4ff45e6..04a2d98 100644 --- a/data/calculateStats.py +++ b/data/calculateStats.py @@ -36,18 +36,14 @@ def convertTimeIntoHumanReadable(__seconds: int) -> str: # Iterate through the myDateTime dict and formulate a list of the values. # If the delimeter field is 0, don't include it in final result - for field in myDateTime: - if myDateTime[field] > 1: + for field, value in myDateTime.items(): + if value > 1: humanReadable = f"{myDateTime[field]} {field}" timeYMDHMS.append(humanReadable) - else: - if myDateTime[field] == 1: - humanReadable = f"{myDateTime[field]} {field[:-1]}" - timeYMDHMS.append(humanReadable) - myDateTimeString = ', '.join(timeYMDHMS) - - # Return the human readable form string. - return myDateTimeString + elif myDateTime[field] == 1: + humanReadable = f"{myDateTime[field]} {field[:-1]}" + timeYMDHMS.append(humanReadable) + return ', '.join(timeYMDHMS) def calculateStatistics(self, __certResults: dict) -> dict: """Returns statistics based off certificate information provided.""" @@ -93,13 +89,13 @@ def calculateStatistics(self, __certResults: dict) -> dict: avgTemplateTimeSeconds += item["certificateTemplateTime"] # Calculate lowest certificate template time. - if lowestCertificateTemplateTime > item["certificateTemplateTime"]: - lowestCertificateTemplateTime = item["certificateTemplateTime"] - + lowestCertificateTemplateTime = min( + lowestCertificateTemplateTime, item["certificateTemplateTime"] + ) # Calculate highest certificate template time. - if highestCertificateTemplateTime < item["certificateTemplateTime"]: - highestCertificateTemplateTime = item["certificateTemplateTime"] - + highestCertificateTemplateTime = max( + highestCertificateTemplateTime, item["certificateTemplateTime"] + ) caIssuerCommonName = item["certificateInfo"]["certificateIssuer"]["commonName"] # Calculate common Certificate Authority Issuers @@ -170,8 +166,7 @@ def combineData(self, __certResults: dict, __mySystemInfo: dict, __scriptStartTi # Get all the statistics for the measurements performed statistics = self.calculateStatistics(__certResults) - # Create the json script structure with all the meta data. - myData = { + return { "tenantId": __mySystemInfo.myConfigJson["myTenantId"], "deviceId": __mySystemInfo.myConfigJson["myDeviceId"], "deviceTag": __mySystemInfo.myConfigJson["myTags"], @@ -182,22 +177,32 @@ def combineData(self, __certResults: dict, __mySystemInfo: dict, __scriptStartTi "scriptEndTime": scriptEndTime, "scriptExecutionTime": scriptExecutionTime, "averageQueryTime": statistics["averageQueryTime"], - "averageCertificateUtilization": statistics["averageCertificateUtilization"], + "averageCertificateUtilization": statistics[ + "averageCertificateUtilization" + ], "averageTemplateTime": statistics["averageTemplateTimeSeconds"], - "averageTemplateTimeHumanReadable": statistics["averageTemplateTimeHumanReadable"], - "lowestCertificateTemplateTime": statistics["lowestCertificateTemplateTime"], - "lowestCertificateTemplateTimeHumanReadable": statistics["lowestCertificateTemplateTimeHumanReadable"], - "highestCertificateTemplateTime": statistics["highestCertificateTemplateTime"], - "highestCertificateTemplateTimeHumanReadable": statistics["highestCertificateTemplateTimeHumanReadable"], + "averageTemplateTimeHumanReadable": statistics[ + "averageTemplateTimeHumanReadable" + ], + "lowestCertificateTemplateTime": statistics[ + "lowestCertificateTemplateTime" + ], + "lowestCertificateTemplateTimeHumanReadable": statistics[ + "lowestCertificateTemplateTimeHumanReadable" + ], + "highestCertificateTemplateTime": statistics[ + "highestCertificateTemplateTime" + ], + "highestCertificateTemplateTimeHumanReadable": statistics[ + "highestCertificateTemplateTimeHumanReadable" + ], "commonCAIssuersCount": statistics["commonCAIssuersCount"], "commonCipherInfoCount": statistics["commonCipherInfoCount"], - "numberofTests": statistics["numberOfTests"] + "numberofTests": statistics["numberOfTests"], }, - "certResults": __certResults + "certResults": __certResults, } - return myData - def __init__(self): """Initialize the calculateStats class.""" self.initialized = True diff --git a/data/certData.py b/data/certData.py index 861db9f..edec82f 100644 --- a/data/certData.py +++ b/data/certData.py @@ -38,7 +38,7 @@ def getFileFromURL(fileURL: str) -> list: print('Too many redirects while accessing the URL') tmpData = ['URL Redirects too many'] except requests.exceptions.ConnectionError: - print('Could not connect to URL - ' + fileURL + '\n') + print(f'Could not connect to URL - {fileURL}' + '\n') tmpData = ['URL connection error'] return tmpData @@ -69,7 +69,7 @@ def parse_line(line: str) -> dict: if '[' in line: line, options = line.split('[', 1) # Remove the closing bracket and convert to a list - options = ast.literal_eval('[' + options) + options = ast.literal_eval(f'[{options}') # Check if port number exists if ':' in line: @@ -95,14 +95,18 @@ def loadQueriesFile(queriesFile: str) -> list: hostEntry = certData.parse_line(line) queries.append(hostEntry) - elif path.exists(queriesFile) and not (queriesFile.startswith('http://') or queriesFile.startswith('https://')): + elif ( + path.exists(queriesFile) + and not queriesFile.startswith('http://') + and not queriesFile.startswith('https://') + ): with open(queriesFile, "r", encoding="utf-8") as f_queryFile: queryFile = f_queryFile.readlines() for line in queryFile: hostEntry = certData.parse_line(line) queries.append(hostEntry) else: - print('I cannot get file ' + queriesFile) + print(f'I cannot get file {queriesFile}') sys.exit(1) return queries diff --git a/data/emailTemplateBuilder.py b/data/emailTemplateBuilder.py index 225e06a..9f6c2e0 100644 --- a/data/emailTemplateBuilder.py +++ b/data/emailTemplateBuilder.py @@ -55,10 +55,7 @@ def monitoredHostsText(self, __jsonData): # Build the string from all of the components above with the correct formatting. monitoredHostsFormattedText += f"{iHostname:{filler}<{maxHostname}}{iPort:{filler}<6}{iTimeLeft:{filler}<{maxTimeLeft}}{iPercentageUtilization:{filler}<{maxPercentageUtilization}}" + "\n" - # Go through the body text message and replace the MONITOREDHOSTS field with the newly formatted hostnames and ports. - __newBodyText = bodyTextHeaders + monitoredHostsFormattedText - - return __newBodyText + return bodyTextHeaders + monitoredHostsFormattedText def monitoredHostsHtml(self, __jsonData): """Builds out the html template for monitored hosts.""" @@ -75,9 +72,12 @@ def monitoredHostsHtml(self, __jsonData): iPercentageUtilization = entry["certificateInfo"]["percentageUtilization"] monitoredHostsFormattedHtml += f"{iHostname}{iPort}{iTimeLeft}{iPercentageUtilization}\n" - __newBodyHtml = "\n" + monitoredHostsFormattedHtmlHeaders + monitoredHostsFormattedHtml + "
\n" - - return __newBodyHtml + return ( + "\n" + + monitoredHostsFormattedHtmlHeaders + + monitoredHostsFormattedHtml + + "
\n" + ) def buildEmailFromTextTemplate(self, __jsonData): """Modifies a text file template based off the submitted hosts.""" diff --git a/data/sendDataMongoDB.py b/data/sendDataMongoDB.py index 068254d..a7bb663 100644 --- a/data/sendDataMongoDB.py +++ b/data/sendDataMongoDB.py @@ -22,8 +22,7 @@ def loadConfigurationFile(__fileName="mongo.cfg"): try: with open(__fileName) as fileNameMongo: __mongoConfig = fileNameMongo.read() - __mongoConfigJson = json.loads(__mongoConfig) - return __mongoConfigJson + return json.loads(__mongoConfig) except FileNotFoundError: print(f"Cannot find file {__fileName}.") sys.exit(1) @@ -89,8 +88,6 @@ def sendResults(self, __results, __destCollection): # First check to see if we need to attempt to upload previous data that was not uploaded. previousJsonScriptData = self.getJsonScriptDataFromFile("certificateData.json") previousUploadResult = [] - __uploadResult = [] - while len(previousJsonScriptData) > 0: jsonScriptDataItem = previousJsonScriptData.pop(0) previousUploadResultItem = __destCollection.insert_one(jsonScriptDataItem) @@ -105,7 +102,7 @@ def sendResults(self, __results, __destCollection): previousJsonScriptData = [] __mongoResult = __destCollection.insert_one(__results) - __uploadResult.append(__mongoResult) + __uploadResult = [__mongoResult] except pymongo.errors.ServerSelectionTimeoutError: # Get time of error errTime = str(datetime.utcnow()) @@ -150,11 +147,10 @@ def connectionString(__destination): # Builds the login credentials to be used. if __mongoUsername == "": __mongoLoginCredentials = "" + elif __mongoPassword == "": + __mongoLoginCredentials = __mongoUsername else: - if __mongoPassword == "": - __mongoLoginCredentials = __mongoUsername - else: - __mongoLoginCredentials = f"{__mongoUsername}:{__mongoPassword}" + __mongoLoginCredentials = f"{__mongoUsername}:{__mongoPassword}" # Check to see if __mongoUri is an IP address or not. if "cluster" in __destination and __destination["cluster"] is True: @@ -174,11 +170,11 @@ def connectionString(__destination): else: __collectionName = "certdataGlobal" - if __mongoLoginCredentials == "": - __mongoConnectionString = f"mongodb{__srv}://{__mongoUri}/{__tls}" - else: - __mongoConnectionString = f"mongodb{__srv}://{__mongoLoginCredentials}@{__mongoUri}/{__collectionName}{__tls}" - return __mongoConnectionString + return ( + f"mongodb{__srv}://{__mongoUri}/{__tls}" + if __mongoLoginCredentials == "" + else f"mongodb{__srv}://{__mongoLoginCredentials}@{__mongoUri}/{__collectionName}{__tls}" + ) def createDB(self, __destination): """create a destination database to upload the data to.""" @@ -233,9 +229,7 @@ def uploadDataToMongoDB(self, __jsonScriptData): iResult["startTime"] = datetime.fromisoformat(iResult["startTime"]) iResult["endTime"] = datetime.fromisoformat(iResult["endTime"]) - uploadResult = self.sendResults(__jsonScriptData, collection) - - return uploadResult + return self.sendResults(__jsonScriptData, collection) def __init__(self): """Initialize the sendDataMongoDB class.""" diff --git a/systemInfo/systemInfo.py b/systemInfo/systemInfo.py index 48c68b4..30df5c4 100644 --- a/systemInfo/systemInfo.py +++ b/systemInfo/systemInfo.py @@ -31,21 +31,15 @@ def updateMyConfig(self): def getDeviceId(self): """Get the uuid.""" - __myDeviceId = "" - if "myDeviceId" in self.myConfigJson: - __myDeviceId = self.myConfigJson["myDeviceId"] - return __myDeviceId + return ( + self.myConfigJson["myDeviceId"] + if "myDeviceId" in self.myConfigJson + else "" + ) def getTag(self): """Get the tag(s).""" - __myTags = [] - - # First check to see if the myTags element is in the myConfigJson variable. - if "myTags" in self.myConfigJson: - __myTags = self.myConfigJson["myTags"] - - # Return the value of __myTags - return __myTags + return self.myConfigJson["myTags"] if "myTags" in self.myConfigJson else [] def setTag(self, __tagName, __writeConfig): """Set the tag for data aggregation purposes.""" @@ -95,10 +89,7 @@ def checkMyTenantId(__myConfigJson): Returns True if it is. Returns False if it is not """ - result = False - if "myTenantId" in __myConfigJson and __myConfigJson["myTenantId"] != "": - result = True - return result + return "myTenantId" in __myConfigJson and __myConfigJson["myTenantId"] != "" @staticmethod def checkMyTags(__myConfigJson): @@ -107,8 +98,7 @@ def checkMyTags(__myConfigJson): Returns True if it is. Returns False if it is not. """ - result = bool("myTags" in __myConfigJson) - return result + return "myTags" in __myConfigJson @staticmethod def checkMyDeviceId(__myConfigJson): @@ -117,10 +107,7 @@ def checkMyDeviceId(__myConfigJson): Returns True if it is. Returns False if is not. """ - result = False - if "myDeviceId" in __myConfigJson and __myConfigJson["myDeviceId"] != "": - result = True - return result + return "myDeviceId" in __myConfigJson and __myConfigJson["myDeviceId"] != "" def checkIfTenantIdExists(self, __myConfigJson): """Check to see if the Tenant Id is defined."""