Skip to content

Commit

Permalink
v1.9.12 (#27)
Browse files Browse the repository at this point in the history
  • Loading branch information
gattjoe authored Oct 5, 2023
1 parent 3b8583c commit 1628dde
Show file tree
Hide file tree
Showing 9 changed files with 96 additions and 126 deletions.
19 changes: 11 additions & 8 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,21 @@ jobs:
steps:
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # tag=v3.3.0
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # tag=v4.5.0
uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # tag=v4.7.1
with:
python-version: ${{ matrix.python-version }}
- name: Install tools
run: |
python -m pip install --upgrade pip setuptools wheel
- name: Install tox
- name: Install pytest
run: |
python -m pip install tox
- name: Run tox
python -m pip install pytest pytest-cov
- name: Install requirements
run: |
tox -e py
python -m pip install -r requirements.txt
- name: Run pytest
run: |
pytest -v --junitxml=test-output.xml --cov=ocspchecker --cov-report xml
- name: Upload test results
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # tag=v3.1.2
with:
Expand All @@ -50,17 +53,17 @@ jobs:
steps:
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # tag=v3.3.0
- name: Set up Python 3.x
uses: actions/setup-python@v2
uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # tag=v4.7.1
with:
python-version: "3.9"
architecture: "x64"
- name: Install tools
run: |
python -m pip install --upgrade pip setuptools wheel
python -m pip install twine
python -m pip install build twine
- name: Build pypy package
run: |
python -m setup sdist bdist_wheel
python -m build
- name: Check package description
run: |
twine check dist/*
Expand Down
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,10 @@
- moved to pyproject.toml for project definition
- added tests for python 3.10 and 3.11
- added coverage across macOS, Linux, and Windows
- fixed two broken tests and commented one out for now
- fixed two broken tests and commented one out for now

# v1.9.12
- removed validators
- bump all dependencies
- added dev-requirements.txt for CI
- removed tox
5 changes: 5 additions & 0 deletions dev-requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pytest==7.4.2
twine==4.0.2
pylint==2.17.2
black==23.9.1
isort==5.12.0
2 changes: 1 addition & 1 deletion ocspchecker/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
""" __init__.py """

__title__ = "ocsp-checker"
__version__ = "1.9.9"
__version__ = "1.9.12"
__author__ = "Joe Gatt"

from ocspchecker.ocspchecker import get_ocsp_status
120 changes: 33 additions & 87 deletions ocspchecker/ocspchecker.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,35 @@
For a short-term fix, I will use nassl to grab the full cert chain. """

from pathlib import Path
from socket import AF_INET, SOCK_STREAM, gaierror, socket, timeout
from typing import Any, List
from urllib import error, request
from socket import gaierror, timeout, socket, SOCK_STREAM, AF_INET
from urllib.parse import urlparse
from urllib import request, error
from typing import List, Any
from pathlib import Path

import certifi
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.hashes import SHA1
from cryptography.x509 import ExtensionNotFound, ocsp
from cryptography.x509.oid import ExtensionOID
from nassl._nassl import OpenSSLError
from nassl.cert_chain_verifier import CertificateChainVerificationFailed
from nassl.ssl_client import (
ClientCertificateRequested,
OpenSslVerifyEnum,
OpenSslVersionEnum,
OpenSslVerifyEnum,
SslClient,
)
from validators import domain, url
from cryptography.x509 import load_pem_x509_certificate, ocsp, ExtensionNotFound
from nassl.cert_chain_verifier import CertificateChainVerificationFailed
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.hashes import SHA1
from cryptography.x509.oid import ExtensionOID
from nassl._nassl import OpenSSLError
import certifi


class InitialConnectionError(Exception):
"""Custom exception class to differentiate between
initial connection errors and OpenSSL errors"""

pass

class OcspResponderError(Exception):
"""Custom exception class to identify errors obtaining a response from a CA'a Responder"""


# Get the local path to the ca certs
Expand Down Expand Up @@ -63,7 +63,7 @@ class InitialConnectionError(Exception):


def get_ocsp_status(host: str, port: Any = None) -> list:
"""Main function with two inputs: host, and port.
"""Main function with two inputs: host and port.
Port defaults to TCP 443"""

port = port or 443
Expand All @@ -74,14 +74,6 @@ def get_ocsp_status(host: str, port: Any = None) -> list:
# pylint: disable=W0703
# All of the exceptions in this function are passed-through

# Validate port
try:
port = verify_port(port)

except Exception as err:
results.append("Error: " + str(err))
return results

# Sanitize host
try:
host = verify_host(host)
Expand Down Expand Up @@ -117,7 +109,6 @@ def get_ocsp_status(host: str, port: Any = None) -> list:


def get_certificate_chain(host: str, port: int) -> List[str]:

"""Connect to the host on the port and obtain certificate chain"""

func_name: str = "get_certificate_chain"
Expand All @@ -143,14 +134,10 @@ def get_certificate_chain(host: str, port: int) -> List[str]:
) from None

except ConnectionRefusedError:
raise InitialConnectionError(
f"{func_name}: Connection to {host}:{port} refused."
) from None
raise InitialConnectionError(f"{func_name}: Connection to {host}:{port} refused.") from None

except OSError:
raise InitialConnectionError(
f"{func_name}: Unable to reach the host {host}."
) from None
raise InitialConnectionError(f"{func_name}: Unable to reach the host {host}.") from None

except (OverflowError, TypeError):
raise InitialConnectionError(
Expand All @@ -172,19 +159,13 @@ def get_certificate_chain(host: str, port: int) -> List[str]:
cert_chain = ssl_client.get_verified_chain()

except IOError:
raise ValueError(
f"{func_name}: {host} did not respond to the Client Hello."
) from None
raise ValueError(f"{func_name}: {host} did not respond to the Client Hello.") from None

except CertificateChainVerificationFailed:
raise ValueError(
f"{func_name}: Certificate Verification failed for {host}."
) from None
raise ValueError(f"{func_name}: Certificate Verification failed for {host}.") from None

except ClientCertificateRequested:
raise ValueError(
f"{func_name}: Client Certificate Requested for {host}."
) from None
raise ValueError(f"{func_name}: Client Certificate Requested for {host}.") from None

except OpenSSLError as err:
for key, value in openssl_errors.items():
Expand All @@ -201,7 +182,6 @@ def get_certificate_chain(host: str, port: int) -> List[str]:


def extract_ocsp_url(cert_chain: List[str]) -> str:

"""Parse the leaf certificate and extract the access method and
access location AUTHORITY_INFORMATION_ACCESS extensions to
get the ocsp url"""
Expand All @@ -211,9 +191,7 @@ def extract_ocsp_url(cert_chain: List[str]) -> str:
ocsp_url: str = ""

# Convert to a certificate object in cryptography.io
certificate = x509.load_pem_x509_certificate(
str.encode(cert_chain[0]), default_backend()
)
certificate = load_pem_x509_certificate(str.encode(cert_chain[0]), default_backend())

# Check to ensure it has an AIA extension and if so, extract ocsp url
try:
Expand All @@ -227,9 +205,7 @@ def extract_ocsp_url(cert_chain: List[str]) -> str:
ocsp_url = aia_method.__getattribute__("access_location").value

if ocsp_url == "":
raise ValueError(
f"{func_name}: OCSP URL missing from Certificate AIA Extension."
)
raise ValueError(f"{func_name}: OCSP URL missing from Certificate AIA Extension.")

except ExtensionNotFound:
raise ValueError(
Expand All @@ -240,20 +216,15 @@ def extract_ocsp_url(cert_chain: List[str]) -> str:


def build_ocsp_request(cert_chain: List[str]) -> bytes:

"""Build an OCSP request out of the leaf and issuer pem certificates
see: https://cryptography.io/en/latest/x509/ocsp/#cryptography.x509.ocsp.OCSPRequestBuilder
for more information"""

func_name: str = "build_ocsp_request"

try:
leaf_cert = x509.load_pem_x509_certificate(
str.encode(cert_chain[0]), default_backend()
)
issuer_cert = x509.load_pem_x509_certificate(
str.encode(cert_chain[1]), default_backend()
)
leaf_cert = load_pem_x509_certificate(str.encode(cert_chain[0]), default_backend())
issuer_cert = load_pem_x509_certificate(str.encode(cert_chain[1]), default_backend())

except ValueError:
raise Exception(f"{func_name}: Unable to load x509 certificate.") from None
Expand All @@ -268,14 +239,10 @@ def build_ocsp_request(cert_chain: List[str]) -> bytes:


def get_ocsp_response(ocsp_url: str, ocsp_request_data: bytes):

"""Send OCSP request to ocsp responder and retrieve response"""

func_name: str = "get_ocsp_response"

# Confirm that the ocsp_url is a valid url
if not url(ocsp_url):
raise Exception(f"{func_name}: URL failed validation for {ocsp_url}")
ocsp_response = None

try:
ocsp_request = request.Request(
Expand All @@ -289,22 +256,20 @@ def get_ocsp_response(ocsp_url: str, ocsp_request_data: bytes):

except error.URLError as err:
if isinstance(err.reason, timeout):
raise Exception(f"{func_name}: Request timeout for {ocsp_url}") from None
raise OcspResponderError(f"{func_name}: Request timeout for {ocsp_url}")

if isinstance(err.reason, gaierror):
raise Exception(
f"{func_name}: {ocsp_url} is invalid or not known."
) from None
raise OcspResponderError(f"{func_name}: {ocsp_url} is invalid or not known.")

raise OcspResponderError(f"{func_name}: Unknown Connection Error to {ocsp_url}")

raise Exception(
f"{func_name}: Unknown Connection Error to {ocsp_url}"
) from None
except ValueError as err:
raise OcspResponderError(f"{func_name}: Unknown Connection Error to {ocsp_url}")

return ocsp_response


def extract_ocsp_result(ocsp_response):

"""Extract the OCSP result from the provided ocsp_response"""

func_name: str = "extract_ocsp_result"
Expand Down Expand Up @@ -334,21 +299,6 @@ def extract_ocsp_result(ocsp_response):
return f"{func_name}: {str(err)}"


def verify_port(port: Any) -> int:
"""Check port for type and validity"""

if not isinstance(port, int):
if port.isnumeric() is False:
raise Exception(f"Invalid port: '{port}'. Port must be between 0-65535.")

_port = int(port)

if _port > 65535 or _port == 0:
raise Exception(f"Invalid port: '{port}'. Port must be between 0-65535.")

return _port


def verify_host(host: str) -> str:
"""Parse a DNS name to ensure it does not contain http(s)"""
parsed_name = urlparse(host)
Expand All @@ -358,8 +308,4 @@ def verify_host(host: str) -> str:
if host_candidate == "":
host_candidate = parsed_name.path

# The below ensures a valid domain was supplied
if not domain(host_candidate):
raise Exception(f"{host} is not a valid FQDN.")

return host_candidate
38 changes: 34 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name="ocsp-checker"
version="1.9.11"
version="1.9.12"
description="Library used to check the OCSP revocation status for a x509 digital certificate."

readme= {file = "README.md", content-type = "text/markdown"}
Expand All @@ -21,9 +21,8 @@ classifiers=[
]
keywords=["ssl, tls, ocsp, python, security"]
dependencies = [
"cryptography~=39.0",
"cryptography~=41.0",
"nassl~=5.0",
"validators>=0.20.0",
"certifi",
]
requires-python = ">=3.7"
Expand All @@ -38,4 +37,35 @@ find = {}
"changelog" = "https://github.com/MetLife/OCSPChecker/blob/master/CHANGELOG.md"

[project.scripts]
ocspchecker = "ocspchecker.__main__:main"
ocspchecker = "ocspchecker.__main__:main"


[tool.black]
line-length = 100
target-version = ["py37", "py38", "py39", "py310", "py311"]

[tool.isort]
ensure_newline_before_comments = true
force_grid_wrap = 0
force_sort_within_sections = true
include_trailing_comma = true
known_local_folder = ["ocspchecker"]
length_sort = true
line_length = 100
multi_line_output = 3
no_sections = false
profile = "black"
py_version=311
reverse_relative = true
reverse_sort = true
skip_gitignore = true
use_parentheses = true

[tool.pyright]
root = ["ocspchecker"]
include = ["ocspchecker" , "tests"]
reportMissingImports = true
reportMissingTypeStubs = false
pythonPlatform = "All"
pythonVersion = "3.11"
typeCheckingMode = "basic"
5 changes: 1 addition & 4 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
certifi
cffi==1.15.1
cryptography==39.0.2
decorator==5.1.1
cryptography==41.0.4
nassl==5.0.1
pycparser==2.21
six==1.16.0
validators==0.20.0
Loading

0 comments on commit 1628dde

Please sign in to comment.