Last active
August 3, 2023 06:28
-
-
Save palmerc/eaebb168fed0689ae9a9f73833727950 to your computer and use it in GitHub Desktop.
Apple OCSP Check
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import argparse | |
import pathlib | |
from cryptography import x509 | |
from cryptography.x509 import ocsp | |
from cryptography.hazmat.primitives import serialization, hashes | |
import requests | |
class OCSPChecker: | |
def __init__(self, leaf=None, issuer=None, oscp_url='http://ocsp.apple.com/'): | |
self._issuer = OCSPChecker._load_der(issuer) | |
self._leaf = OCSPChecker._load_der(leaf) | |
self._ocsp_url = oscp_url | |
self._session = requests.Session() | |
self._session.headers = {'Content-Type': 'application/ocsp-request'} | |
def set_leaf(self, cert): | |
self._leaf = cert | |
def check(self): | |
response = self._session.post(self._ocsp_url, data=self._der_request_data()) | |
ocsp_response = ocsp.load_der_ocsp_response(response.content) | |
if ocsp_response.response_status.value != 0: | |
raise Exception(f'OCSP Request Error: {ocsp_response.response_status}') | |
return ocsp_response | |
def details(self): | |
x509_cert = self._leaf | |
org_name = x509_cert.subject.get_attributes_for_oid(x509.OID_ORGANIZATION_NAME).pop().value | |
org_id = x509_cert.subject.get_attributes_for_oid(x509.OID_ORGANIZATIONAL_UNIT_NAME).pop().value | |
cert_serial_number = OCSPChecker.to_apple_hex(x509_cert.serial_number) | |
cert_fingerprint_sha1 = OCSPChecker.to_apple_hex(x509_cert.fingerprint(hashes.SHA1()).hex()) | |
cert_fingerprint_sha256 = OCSPChecker.to_apple_hex(x509_cert.fingerprint(hashes.SHA256()).hex()) | |
return { | |
'name': org_name, | |
'id': org_id, | |
'sn': cert_serial_number, | |
'sha1': cert_fingerprint_sha1, | |
'sha256': cert_fingerprint_sha256 | |
} | |
@classmethod | |
def to_hex(cls, number, padding=False): | |
if isinstance(number, str): | |
try: | |
number = int(number, 16) | |
except ValueError: | |
raise Exception(f'String "{str}" is not a valid hex number.') | |
s = format(number, 'x') | |
length = len(s) | |
if padding and bool(length % 2): | |
s = format(number, f'0{length + 1}x') | |
return s | |
@classmethod | |
def to_apple_hex(cls, number): | |
s = OCSPChecker.to_hex(number, padding=True).upper() | |
return ' '.join(a + b for a, b in zip(s[::2], s[1::2])) | |
def _der_request_data(self): | |
builder = ocsp.OCSPRequestBuilder() | |
builder = builder.add_certificate(self._leaf, self._issuer, hashes.SHA256()) | |
ocsp_data = builder.build() | |
return ocsp_data.public_bytes(serialization.Encoding.DER) | |
@classmethod | |
def _load_der(cls, path): | |
with open(path, 'rb') as f: | |
cert_bytes = f.read() | |
return x509.load_der_x509_certificate(cert_bytes) | |
def find_certs(file): | |
path = pathlib.Path(file) | |
if path.is_dir(): | |
certificates = path.glob('*.cer') | |
else: | |
certificates = [path] | |
return certificates | |
def check_ocsp_status(leaf, issuer): | |
certs = find_certs(leaf) | |
for cert in certs: | |
checker = OCSPChecker(leaf=cert, issuer=issuer) | |
result = checker.check() | |
report = [f"Subject: {checker.details()['name']}", | |
f"ID: {checker.details()['id']}", | |
f"Serial number: {checker.details()['sn']}", | |
f"SHA1: {checker.details()['sha1']}", | |
f"SHA256: {checker.details()['sha256']}"] | |
if result.certificate_status == ocsp.OCSPCertStatus.REVOKED: | |
report += [f'Certificate status: REVOKED:', | |
f'Revocation time: {str(result.revocation_time)}', | |
f"Reason: {str(result.revocation_reason).split('.').pop()}"] | |
elif result.certificate_status == ocsp.OCSPCertStatus.GOOD: | |
report += [f'Certificate status: GOOD'] | |
elif result.certificate_status == ocsp.OCSPCertStatus.UNKNOWN: | |
report += [f'Certificate status: UNKNOWN'] | |
else: | |
raise Exception(f'OCSP Certificate Status Error: {result.certificate_status}') | |
print('\n'.join(report)) | |
print() | |
def main(): | |
parser = argparse.ArgumentParser('Check the OCSP status of a certificate') | |
parser.add_argument('--issuer', default='certificates/apple/AppleWWDRCAG3.cer', | |
help='Specify the issuer certificate to check against') | |
parser.add_argument('file', help='Certificate or directory of files to check') | |
args = parser.parse_args() | |
check_ocsp_status(args.file, args.issuer) | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
You will need the WWDR intermediate from Apple which can be obtained on their webpage:
https://www.apple.com/certificateauthority/
Specifically, WWDR G3
https://www.apple.com/certificateauthority/AppleWWDRCAG3.cer