Skip to content

Commit 0693ad9

Browse files
authored
Merge pull request #14 from gattjoe/1.8
v1.8
2 parents bbe2918 + 13a2161 commit 0693ad9

File tree

7 files changed

+91
-66
lines changed

7 files changed

+91
-66
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,10 @@
2929
- Upgraded cryptography to 3.4.6
3030
- Fixed failing tests
3131
- Added tox tests for Python 3.9
32+
33+
# v1.8.0
34+
- Fixed a bug to handle a situation when parsing a certificate with an AIA extension but no OCSP URL
35+
- Fixed a bug to handle a situation where the remote host is not using SSL/TLS and we attempt to do a SSL/TLS handshake
36+
- Fixed a bug to handle a situation where the remote host does not respond to a Client Hello
37+
- Prepended all exceptions with the function name for easier troubleshooting
38+
- Upgraded cryptography to 3.4.7 to support the latest versions of OpenSSL

README.md

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,31 +5,26 @@
55
[![Python version](https://img.shields.io/pypi/pyversions/ocsp-checker.svg)](https://pypi.org/project/ocsp-checker/)
66

77
## Overview
8-
-----------
98

109
OCSP-Checker is a python package based on Alban Diquet's [nassl](https://github.com/nabla-c0d3/nassl) wrapper and the Python Cryptographic Authority's [cryptography](https://github.com/pyca/cryptography) package. Relying on a web browser to check the revocation status of a x509 digital certificate [has](https://www.imperialviolet.org/2014/04/19/revchecking.html) [been](https://www.imperialviolet.org/2014/04/29/revocationagain.html) [broken](https://scotthelme.co.uk/revocation-is-broken/) from the beginning, and validating certificates outside of the web browser is a manual process. OCSP-Checker aims to solve this by providing an automated means to check the [OCSP](https://en.wikipedia.org/wiki/Online_Certificate_Status_Protocol) revocation status for a x509 digital certificate.
1110

1211

1312
## Pre-requisites
14-
-----------------
1513

1614
__Python__ - Python 3.7 (64-bit) and above.
1715

1816
## Installation
19-
---------------
2017

2118
```pip install ocsp-checker```
2219

2320
## Usage
24-
--------
2521

2622
```
2723
>>> from ocspchecker import ocspchecker
2824
>>> ocsp_request = ocspchecker.get_ocsp_status("github.com")
2925
```
3026

3127
## Sample Output
32-
----------------
3328

3429
Sample output below, let me know if you want to add more fields/information to the output.
3530

@@ -43,7 +38,6 @@ PLEASE NOTE: If you run this on a network with a MITM SSL proxy, you may receive
4338
```
4439

4540
## Command Line Usage
46-
---------------------
4741

4842
OCSP-Checker can now be used at the command line. The format is:
4943
```

ocspchecker/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# __init__.py
22

33
__title__ = "ocsp-checker"
4-
__version__ = "1.7.0"
4+
__version__ = "1.8.0"
55
__author__ = "Joe Gatt"
66

77
from ocspchecker.ocspchecker import get_ocsp_status

ocspchecker/ocspchecker.py

Lines changed: 48 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def get_ocsp_status(host: str, port: Any = None) -> list:
2323
""" Main function with two inputs: host, and port.
2424
Port defaults to TCP 443 """
2525

26-
results = []
26+
results: list = []
2727
results.append(f"Host: {host}:{port}")
2828

2929
# pylint: disable=W0703
@@ -49,34 +49,19 @@ def get_ocsp_status(host: str, port: Any = None) -> list:
4949
return results
5050

5151
try:
52+
# Get the remote certificate chain
5253
cert_chain = get_certificate_chain(host, port)
5354

54-
except Exception as err:
55-
results.append("Error: " + str(err))
56-
return results
57-
58-
try:
55+
# Extract OCSP URL from leaf certificate
5956
ocsp_url = extract_ocsp_url(cert_chain)
6057

61-
except Exception as err:
62-
results.append("Error: " + str(err))
63-
return results
64-
65-
try:
58+
# Build OCSP request
6659
ocsp_request = build_ocsp_request(cert_chain)
6760

68-
except Exception as err:
69-
results.append("Error: " + str(err))
70-
return results
71-
72-
try:
61+
# Send OCSP request to responder and get result
7362
ocsp_response = get_ocsp_response(ocsp_url, ocsp_request)
7463

75-
except Exception as err:
76-
results.append("Error: " + str(err))
77-
return results
78-
79-
try:
64+
# Extract OCSP result from OCSP response
8065
ocsp_result = extract_ocsp_result(ocsp_response)
8166

8267
except Exception as err:
@@ -91,10 +76,11 @@ def get_ocsp_status(host: str, port: Any = None) -> list:
9176

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

94-
""" Connect to the host on the port and obtain certificate chain.
95-
TODO: Tests against WantReadError and WantX509LookupError needed. """
79+
""" Connect to the host on the port and obtain certificate chain """
80+
81+
func_name: str = "get_certificate_chain"
9682

97-
cert_chain = []
83+
cert_chain: list = []
9884

9985
soc = socket(AF_INET, SOCK_STREAM, proto=0)
10086
soc.settimeout(3)
@@ -103,42 +89,49 @@ def get_certificate_chain(host: str, port: int) -> List[str]:
10389
soc.connect((host, port))
10490

10591
except gaierror:
106-
raise Exception(f"{host}:{port} is invalid or not known.") from None
92+
raise Exception(f"{func_name}: {host}:{port} is invalid or not known.") from None
10793

10894
except timeout:
109-
raise Exception(f"Connection to {host}:{port} timed out.") from None
95+
raise Exception(f"{func_name}: Connection to {host}:{port} timed out.") from None
11096

111-
except OverflowError:
112-
raise Exception(f"Illegal port: {port}. Port must be between 0-65535.") from None
97+
except (OverflowError, TypeError):
98+
raise Exception(f"{func_name}: Illegal port: {port}. Port must be between 0-65535.") from None
11399

114-
except TypeError:
115-
raise Exception(f"Illegal port: {port}. Port must be between 0-65535.") from None
100+
except ConnectionRefusedError:
101+
raise Exception(f"{func_name}: Connection to {host}:{port} refused.") from None
116102

117103
ssl_client = SslClient(
118104
ssl_version=OpenSslVersionEnum.SSLV23,
119105
underlying_socket=soc,
120106
ssl_verify=OpenSslVerifyEnum.NONE
121107
)
122108

123-
# Add Server Name Indication (SNI) extension to the CLIENT HELLO
109+
# Add Server Name Indication (SNI) extension to the Client Hello
124110
ssl_client.set_tlsext_host_name(host)
125111

126112
try:
127113
ssl_client.do_handshake()
128114
cert_chain = ssl_client.get_received_chain()
129115

116+
except IOError as err:
117+
raise ValueError(f"{func_name}: {host} did not respond to the Client Hello.") from None
118+
130119
except WantReadError as err:
131-
raise ValueError(err.strerror) from None
120+
raise ValueError(f"{func_name}: err.strerror") from None
132121

133122
except WantX509LookupError as err:
134-
raise ValueError(err.strerror) from None
123+
raise ValueError(f"{func_name}: err.strerror") from None
135124

136125
except OpenSSLError as err:
137-
raise ValueError(err) from None
126+
if "1408F10B" in err.args[0]:
127+
# https://github.com/openssl/openssl/issues/6805
128+
raise ValueError(f"{func_name}: Remote host is not using SSL/TLS on port: {port}") from None
129+
130+
raise ValueError(f"{func_name}: err") from None
138131

139132
finally:
133+
# shutdown() will also close the underlying socket
140134
ssl_client.shutdown()
141-
soc = None
142135

143136
return cert_chain
144137

@@ -149,7 +142,9 @@ def extract_ocsp_url(cert_chain: List[str]) -> str:
149142
access location AUTHORITY_INFORMATION_ACCESS extensions to
150143
get the ocsp url """
151144

152-
ocsp_url = ""
145+
func_name: str = "extract_ocsp_url"
146+
147+
ocsp_url: str = ""
153148

154149
# Convert to a certificate object in cryptography.io
155150
certificate = x509.load_pem_x509_certificate(
@@ -167,9 +162,12 @@ def extract_ocsp_url(cert_chain: List[str]) -> str:
167162
if aia_extensions[index].access_method._name == "OCSP":
168163
ocsp_url = aia_extensions[index].access_location.value
169164

165+
if ocsp_url == "":
166+
raise ValueError(f"{func_name}: OCSP URL missing from Certificate AIA Extension.")
167+
170168
except ExtensionNotFound:
171169
raise ValueError(
172-
"Certificate Authority Information Access (AIA) Extension Missing. Possible MITM Proxy."
170+
f"{func_name}: Certificate Authority Information Access (AIA) Extension Missing. Possible MITM Proxy."
173171
) from None
174172

175173
return ocsp_url
@@ -181,6 +179,8 @@ def build_ocsp_request(cert_chain: List[str]) -> bytes:
181179
see: https://cryptography.io/en/latest/x509/ocsp/#cryptography.x509.ocsp.OCSPRequestBuilder
182180
for more information """
183181

182+
func_name: str = "build_ocsp_request"
183+
184184
try:
185185
leaf_cert = x509.load_pem_x509_certificate(
186186
str.encode(cert_chain[0]), default_backend()
@@ -190,7 +190,7 @@ def build_ocsp_request(cert_chain: List[str]) -> bytes:
190190
)
191191

192192
except ValueError:
193-
raise Exception("Unable to load x509 certificate.") from None
193+
raise Exception(f"{func_name}: Unable to load x509 certificate.") from None
194194

195195
# Build OCSP request
196196
builder = ocsp.OCSPRequestBuilder()
@@ -205,9 +205,11 @@ def get_ocsp_response(ocsp_url: str, ocsp_request_data: bytes):
205205

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

208+
func_name: str = "get_ocsp_response"
209+
208210
# Confirm that the ocsp_url is a valid url
209211
if not url(ocsp_url):
210-
raise Exception(f"URL failed validation for {ocsp_url}")
212+
raise Exception(f"{func_name}: URL failed validation for {ocsp_url}")
211213

212214
try:
213215
ocsp_response = requests.post(
@@ -218,13 +220,13 @@ def get_ocsp_response(ocsp_url: str, ocsp_request_data: bytes):
218220
)
219221

220222
except requests.exceptions.Timeout:
221-
raise Exception(f"Request timeout for {ocsp_url}") from None
223+
raise Exception(f"{func_name}: Request timeout for {ocsp_url}") from None
222224

223225
except requests.exceptions.ConnectionError:
224-
raise Exception(f"Unknown Connection Error to {ocsp_url}") from None
226+
raise Exception(f"{func_name}: Unknown Connection Error to {ocsp_url}") from None
225227

226228
except requests.exceptions.RequestException:
227-
raise Exception(f"Unknown Connection Error to {ocsp_url}") from None
229+
raise Exception(f"{func_name}: Unknown Connection Error to {ocsp_url}") from None
228230

229231
return ocsp_response
230232

@@ -233,6 +235,8 @@ def extract_ocsp_result(ocsp_response):
233235

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

238+
func_name: str = "extract_ocsp_result"
239+
236240
try:
237241
ocsp_response = ocsp.load_der_ocsp_response(ocsp_response.content)
238242
# OCSP Response Status here:
@@ -248,14 +252,14 @@ def extract_ocsp_result(ocsp_response):
248252
# UNAUTHORIZED = 6
249253
ocsp_response = str(ocsp_response.response_status)
250254
ocsp_response = ocsp_response.split(".")
251-
raise Exception(f"OCSP Request Error: {ocsp_response[1]}")
255+
raise Exception(f"{func_name}: OCSP Request Error: {ocsp_response[1]}")
252256

253257
certificate_status = str(ocsp_response.certificate_status)
254258
certificate_status = certificate_status.split(".")
255259
return f"OCSP Status: {certificate_status[1]}"
256260

257261
except ValueError as err:
258-
return f"{str(err)}"
262+
return f"{func_name}: {str(err)}"
259263

260264

261265
def verify_port(port: Any) -> int:

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
cryptography==3.4.6
1+
cryptography==3.4.7
22
nassl==4.0.0
33
requests>=2.24.0
44
validators==0.18.2

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
setup(
1010
name="ocsp-checker",
11-
version="1.7.0",
11+
version="1.8.0",
1212
description="Library used to check the OCSP revocation status for a x509 digital certificate.",
1313
long_description=long_description,
1414
long_description_content_type="text/markdown",
@@ -34,7 +34,7 @@
3434
entry_points={"console_scripts": ["ocspchecker = ocspchecker.__main__:main"]},
3535
# Dependencies
3636
install_requires=[
37-
"cryptography==3.4.6",
37+
"cryptography==3.4.7",
3838
"nassl==4.0.0",
3939
"requests>=2.24",
4040
"validators>=0.18"

0 commit comments

Comments
 (0)