Skip to content

Instantly share code, notes, and snippets.

@palmerc
Last active August 3, 2023 06:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save palmerc/eaebb168fed0689ae9a9f73833727950 to your computer and use it in GitHub Desktop.
Save palmerc/eaebb168fed0689ae9a9f73833727950 to your computer and use it in GitHub Desktop.
Apple OCSP Check
#!/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()
@palmerc
Copy link
Author

palmerc commented Aug 3, 2023

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment