Skip to content

Commit

Permalink
Version 0.57. See CHANGELOG.md
Browse files Browse the repository at this point in the history
  • Loading branch information
Git committed Jan 7, 2024
1 parent 98ad1fd commit a550793
Show file tree
Hide file tree
Showing 9 changed files with 391 additions and 252 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
# 2024/01/06
## Version 0.57
### Enhancements :rocket:
* Adding argument `--save_certificate`. This will save the certificate to disk. Files are stored as a SHA256 hash of the hostname:port combination.
* Adding argument `--output_directory`. This informs the `--save_certificate` option and where it should save the file. If the directory doesn't exist, it'll create it.
* Aligning the code to use classes and make it easier to improve smaller code. Trying to avoid code-bloat! :whale:

### Fixes :wrench:
* Squashing some pesky (and legacy) bugs while displaying content and making code to use classes.

# 2023/12/22
## Version 0.56
### Enhancements :rocket:
Expand Down
35 changes: 29 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Certificate Checker

Version: 0.56
Version: 0.57

Author: TheScriptGuy

Expand All @@ -26,15 +26,18 @@ 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] [--environmentVariables] [--setTag SETTAG] [--delTag] [--getTag] [--renewDeviceId]
[--getDeviceId] [--deleteDeviceId] [--setTenantId SETTENANTID] [--getTenantId] [--delTenantId] [--createBlankConfiguration]
usage: certCheck.py [-h] [--hostname HOSTNAME] [--save_certificate] [--output_directory OUTPUT_DIRECTORY] [--displayCertificate] [--displayCertificateJSON] [--displayScriptDataJSON] [--displayTimeLeft]
[--queryFile QUERYFILE] [--uploadJsonData UPLOADJSONDATA] [--mongoDB] [--sendEmail] [--retryAmount RETRYAMOUNT] [--timeBetweenRetries TIMEBETWEENRETRIES] [--contextVariables] [--environmentVariables]
[--setTag SETTAG] [--delTag] [--getTag] [--renewDeviceId] [--getDeviceId] [--deleteDeviceId] [--setTenantId SETTENANTID] [--getTenantId] [--delTenantId] [--createBlankConfiguration]

Certificate Checker v0.56
Certificate Checker v0.57

optional arguments:
options:
-h, --help show this help message and exit
--hostname HOSTNAME Hostname to get certificate from
--save_certificate Save the certificate to file.
--output_directory OUTPUT_DIRECTORY
Save the file to the certificate directory. Will create directory if it does not exist. Defaults to certificate_directory
--displayCertificate Display certificate info
--displayCertificateJSON
Display certificate info in JSON format
Expand Down Expand Up @@ -68,6 +71,26 @@ optional arguments:
Creates a blank configuration file template - myConfig.json. Overwrites any existing configuration
```
## Saving certificate data to disk
If there is a need to save the files to disk, use the `--save_certificate` and `--output_directory` arguments.
Some examples
```bash
$ python3 certCheck.py --hostname google.com --save_certificate
2024-01-07 00:09:47 - Certificate for host google.com:443 saved to certificate_directory/bb701e2040fc41cbc99c6a152ab6aa6ea8f8488c13321df70efcf132e0fe8e1d.pem...Done
```
Notice that the certificate is saveed into the `certificate_directory` by default.
To adjust the save location, use the `--output_directory` argument.
In this example below, we'll save the certificates into the `saved_certs/2024` directory (neither of these directories exist).
```bash
$ python3 certCheck.py --hostname google.com --save_certificate --output_directory saved_certs/2024
2024-01-07 00:12:14 - Certificate for host google.com:443 saved to saved_certs/2024/bb701e2040fc41cbc99c6a152ab6aa6ea8f8488c13321df70efcf132e0fe8e1d.pem...Done
$ ls -l saved_certs/2024
total 4
-rw-r--r-- 1 programming programming 3701 Jan 6 16:12 bb701e2040fc41cbc99c6a152ab6aa6ea8f8488c13321df70efcf132e0fe8e1d.pem
```
## 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.
Expand Down
63 changes: 41 additions & 22 deletions certCheck.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Program: Certificate Checker
# Author: Nolan Rumble
# Date: 2023/12/20
# Version: 0.56
# Date: 2024/01/06
# Version: 0.57

import argparse
import datetime
Expand All @@ -12,13 +12,14 @@

from systemInfo import systemInfo
from certificate import certificateModule
from certificate import DisplayCertificate
from data import calculateStats
from data import certData
from data import emailTemplateBuilder
from data import sendDataEmail
from mongo import mongo_connection

scriptVersion = "0.56"
scriptVersion = "0.57"

# Global Variables
args = None
Expand All @@ -36,6 +37,12 @@ def parseArguments():
parser.add_argument('--hostname', default='',
help='Hostname to get certificate from')

parser.add_argument('--save_certificate', action='store_true',
help='Save the certificate to file.')

parser.add_argument('--output_directory', default='certificate_directory',
help='Save the file to the certificate directory. Will create directory if it does not exist. Defaults to certificate_directory')

parser.add_argument('--displayCertificate', action='store_true',
help='Display certificate info')

Expand Down Expand Up @@ -204,16 +211,20 @@ def checkArguments(__myCertificate, __jsonCertificateInfo):
"""This will see how the data needs to be displayed to stdout."""
if args.displayCertificateJSON:
# Display the certificate JSON structure
o_myCertificate.printCertInfoJSON(__jsonCertificateInfo)
print_cert_object = DisplayCertificate.DisplayCertificate()
print_cert_object.printCertInfoJSON(__jsonCertificateInfo)

if __myCertificate is not None:
if args.displayCertificate:
o_myCertificate.printCertInfo(__myCertificate)
print_cert_object = DisplayCertificate.DisplayCertificate()
print_cert_object.printCertInfo(__myCertificate["certificateMetaData"])

if args.displayTimeLeft:
# Display the remaining time left on the certificate being queried.
o_myCertificate.printSubject(__myCertificate)
print(" ", o_myCertificate.howMuchTimeLeft(__myCertificate))
print_cert_object = DisplayCertificate.DisplayCertificate()

#print_cert_object.printSubject(__myCertificate["certificateMetaData"])
print(f"{print_cert_object.howMuchTimeLeft(__myCertificate)}")


def emailSendResults(__myJsonScriptData):
Expand Down Expand Up @@ -253,15 +264,19 @@ def processQueryFile():
scriptStartTime = datetime.datetime.utcnow()

for myHostname in myCertData.loadQueriesFile(args.queryFile):
# Set contectVariables to zero
contextVariables = 0
## Set contectVariables to zero
#contextVariables = 0

# Check to see if contextVariables argument was passed.
if args.contextVariables:
contextVariables = 1
## Check to see if contextVariables argument was passed.
#if args.contextVariables:
# contextVariables = 1

# Define initial certificate object
o_myCertificate = certificateModule.certificateModule(contextVariables)
o_myCertificate = certificateModule.certificateModule(
contextVariables=args.contextVariables,
save_certificate=args.save_certificate,
output_directory=args.output_directory
)

# For SSL performance measurement - START
o_startTime = datetime.datetime.utcnow()
Expand All @@ -270,7 +285,7 @@ def processQueryFile():
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)

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))
Expand Down Expand Up @@ -318,12 +333,12 @@ 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=args.contextVariables,
save_certificate=args.save_certificate,
output_directory=args.output_directory
)

o_myCertificate = certificateModule.certificateModule(contextVariables)
o_myCertData = certData.certData()

# For SSL performance measurement - START
Expand All @@ -336,6 +351,7 @@ def processHostname():
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))
Expand All @@ -349,17 +365,20 @@ def processHostname():
# Append system data to JSON certificate structure
jsonScriptData = gatherData([jsonCertificateInfo], o_myInfo, o_startTime, o_endTime)

# Create a DisplayCertifcate Object
print_cert_object = DisplayCertificate.DisplayCertificate()

if args.displayCertificateJSON:
# Display the certificate JSON structure
o_myCertificate.printCertInfoJSON(jsonCertificateInfo)
print_cert_object.printCertInfoJSON(jsonCertificateInfo)

if args.displayCertificate:
# Display the certificate properties.
o_myCertificate.printCertInfo(myCertificate)
print_cert_object.printCertInfo(myCertificate['certificateMetaData'])

if args.displayTimeLeft:
# Display the remaining time left on the certificate being queried.
print(o_myCertificate.howMuchTimeLeft(myCertificate))
print_cert_object.printHowMuchTimeLeft(myCertificate['certificateMetaData'])

if args.displayScriptDataJSON:
# Display the certificate and system JSON structure
Expand Down
14 changes: 0 additions & 14 deletions certificate/CertificateDecoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,6 @@ def _parse_name(self, name: x509.Name) -> Tuple[Tuple[Tuple[str, str], ...], ...
for rdn in name.rdns # Use .rdns to iterate over RDNs in the Name
)

# def _get_extension_value(self, cert: x509.Certificate, ext_type, method: Optional[str] = None) -> Union[Tuple[str, ...], None]:
# try:
# ext = cert.extensions.get_extension_for_class(ext_type)
# if method == 'OCSP':
# return tuple(a.access_location.value for a in ext.value if a.access_method == x509.oid.AuthorityInformationAccessOID.OCSP)
# elif method == 'caIssuers':
# return tuple(a.access_location.value for a in ext.value if a.access_method == x509.oid.AuthorityInformationAccessOID.CA_ISSUERS)
# elif isinstance(ext.value, x509.SubjectAlternativeName):
# return tuple(("DNS", name.value) for name in ext.value)
# elif isinstance(ext.value, x509.CRLDistributionPoints):
# return tuple(dp.full_name[0].value for dp in ext.value)
# except x509.ExtensionNotFound:
# return None

def _get_extension_value(self, cert: x509.Certificate, ext_type, method: Optional[str] = None) -> Union[Tuple[str, ...], None]:
try:
ext = cert.extensions.get_extension_for_class(ext_type)
Expand Down
55 changes: 55 additions & 0 deletions certificate/CertificateStatistics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import datetime
from dateutil.relativedelta import relativedelta

from . import CertificateTimeFormat

class CertificateStatistics:
"""This class is responsible for helping with calculations of meta data."""
CLASS_VERSION = "0.01"

certificate_time_format = CertificateTimeFormat.CertificateTimeFormat().cert_time_format

@staticmethod
def returnNotAfter(__certificateObject) -> None:
"""Return the notAfter field from the certificate."""
if __certificateObject is not None:
return __certificateObject['notAfter']
return ""

@staticmethod
def howMuchTimeLeft(__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(
CertificateStatistics.returnNotAfter(
__certificateObject
),
CertificateStatistics.certificate_time_format
)

__delta = relativedelta(certNotAfter, timeNow)

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]}")

certResult = ', '.join(timeLeft)
else:
certResult = "Invalid certificate"
return certResult


4 changes: 4 additions & 0 deletions certificate/CertificateTimeFormat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
class CertificateTimeFormat:
"""Defines the time format for usage in Certificates."""
CLASS_VERSION = "0.01"
cert_time_format = "%b %d %H:%M:%S %Y %Z"
Loading

0 comments on commit a550793

Please sign in to comment.