Skip to content

Instantly share code, notes, and snippets.

@laanwj
Last active January 10, 2023 17:44
Show Gist options
  • Save laanwj/a0e00bcd3fe4cd2aa1c0803e91310495 to your computer and use it in GitHub Desktop.
Save laanwj/a0e00bcd3fe4cd2aa1c0803e91310495 to your computer and use it in GitHub Desktop.
Validate cryptographic signature on macos macho binary
#!/usr/bin/env python3
import io
import struct
import sys
import pprint
import macholib.MachO
from macholib.mach_o import LC_CODE_SIGNATURE
import asn1crypto.x509
from asn1crypto.cms import ContentInfo, SignedData, CMSAttributes
from oscrypto import asymmetric
from certvalidator.context import ValidationContext
import certvalidator
# Apple root certificate
APPLE_ROOT = b'0\x82\x04\x040\x82\x02\xec\xa0\x03\x02\x01\x02\x02\x08\x18z\xa9\xa8\xc2\x96!\x0c0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x0b\x05\x000b1\x0b0\t\x06\x03U\x04\x06\x13\x02US1\x130\x11\x06\x03U\x04\n\x13\nApple Inc.1&0$\x06\x03U\x04\x0b\x13\x1dApple Certification Authority1\x160\x14\x06\x03U\x04\x03\x13\rApple Root CA0\x1e\x17\r120201221215Z\x17\r270201221215Z0y1-0+\x06\x03U\x04\x03\x0c$Developer ID Certification Authority1&0$\x06\x03U\x04\x0b\x0c\x1dApple Certification Authority1\x130\x11\x06\x03U\x04\n\x0c\nApple Inc.1\x0b0\t\x06\x03U\x04\x06\x13\x02US0\x82\x01"0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x000\x82\x01\n\x02\x82\x01\x01\x00\x89vO\x06[\x9aA\xee\xa5#+\x02\xa3_\xd7s?\xc05\xb0\x8b\x84\n?\x06$\x7f\xa7\x95?\xebO\x0e\x93\xaf\xb4\x0e\xd0\xc8>\xe5m\x18\xb3\x1f\xe8\x89G\xbf\xd7\t\x08\xe4\xffV\x98)\x15\xe7\x94\x9d\xb95\xa3\n\xcd\xb4\xc0\xe1\xe2`\xf4\xca\xec)xEii`k_\x8a\x92\xfc\x9e#\xe6:\xc2"\xb31O\x1c\xba\xf2\xb64YB\xee\xb0\xa9\x02\x03\x18\x91\x04\xb6\xb3x.3\x1f\x80E\rEo\xbb\x0eZ[\x7f:\xe7\xd8\x08\xd7\x0b\x0e2m\xfb\x866\xe4l\xab\xc4\x11\x8ap\x84&\xaa\x9fD\xd1\xf1\xb8\xc6{\x94\x17\x9bH\xf7\x0bX\x16\xba#\xc5\x9f\x159~\xca]\xc32_\x0f\xe0R\x7f@\xea\xbe\xac\x08d\x95[\xc9\x1a\x9c\xe5\x80\xca\x1fjD\x1cl>\xc4\xb0&\x1f\x1d\xec{\xaf^\xa0j=G\xa9X\x121? v(m\x1d\x1c\xb0\xc2N\x11i&\x8b\xcb\xd6\xd0\x11\x82\xc9N\x0f\xf1Vt\xd0\xd9\x08Kfx\xa2\xab\xac\xa7\xe2\xd2L\x87Y\xc9\x02\x03\x01\x00\x01\xa3\x81\xa60\x81\xa30\x1d\x06\x03U\x1d\x0e\x04\x16\x04\x14W\x17\xed\xa2\xcf\xdc|\x98\xa1\x10\xe0\xfc\xbe\x87-,\xf2\xe3\x17T0\x0f\x06\x03U\x1d\x13\x01\x01\xff\x04\x050\x03\x01\x01\xff0\x1f\x06\x03U\x1d#\x04\x180\x16\x80\x14+\xd0iG\x94v\t\xfe\xf4k\x8d.@\xa6\xf7GM\x7f\x08^0.\x06\x03U\x1d\x1f\x04\'0%0#\xa0!\xa0\x1f\x86\x1dhttp://crl.apple.com/root.crl0\x0e\x06\x03U\x1d\x0f\x01\x01\xff\x04\x04\x03\x02\x01\x860\x10\x06\n*\x86H\x86\xf7cd\x06\x02\x06\x04\x02\x05\x000\r\x06\t*\x86H\x86\xf7\r\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00B9tk\xa1\xdc\xc6\xa4\x8f7*\x8c\xb3\x1d\nD\xbc\x95,\x7f\xbcY\xb8\xaca\xfb\x07\x90\x922\xb9\xd4\xbf;\xc1P9jDt\xa2\xec[\x1fp\xe5\xaa\xddKl\x1c#q-_\xd1\xc5\x93\xbe\xee\x9b\x8ape\x82\x9d\x16\xe3\x1a\x10\x17\x89-\xa8\xcd\xfd\x0cxXI\x0c(\x7f3\xee\x00z\x1b\xb4v\xac\xb6\xb5\xbbO\xdf\xa8\x1b\x9d\xc8\x19\x97J\x0bVg/\xc2>\xb6\xb3\xc4\x83:\xf0wmt\xc4.#Q\xee\x9a\xa5\x03o`\xf4\xa5H\xa7\x06\xc2\xbbZ\xe2\x1f\x1fFE~\xe4\x97\xf5\'\x10\xb7 "ror\xda\xc6Pu\xc5=%\x8f]\xa3\x00\xe9\x9f6\x8cH9\x8f\xb3;\xea\x90\x80.\x95\x9a`\xf4x\xce\xf4\x0e\nS>\xa2\xfaO\xd8\x1e\xae\x84\x95\x8d2\xbcVM\x89\xe9x\x18\xe0\xac\x9aB\xbazF\x1b\x84\xa2\x89\xce\x14\xe8\x88\xd1X\x8b\xf6\xaeV\xc4,\x05*E\xaf\x0b\xd9K\xa9\x02\x0f4\xac\x88\xc7aU\x89D\xc9\'s\x07\xee\x82\xe5N\xf5p'
# SuperBlob slot IDs
cdInfoSlot = 1 # Info.plist
cdRequirementsSlot = 2 # internal requirements
cdResourceDirSlot = 3 # resource directory
cdTopDirectorySlot = 4 # Application specific slot
cdEntitlementSlot = 5 # embedded entitlement configuration
cdRepSpecificSlot = 6 # for use by disk rep
cdEntitlementDERSlot = 7 # DER representation of entitlements
cdCodeDirectorySlot = 0 # CodeDirectory
cdAlternateCodeDirectorySlots = 0x1000 # alternate CodeDirectory array
cdAlternateCodeDirectoryLimit = 0x1005 # 5+1 hashes should be enough for everyone...
cdSignatureSlot = 0x10000 # CMS signature
cdIdentificationSlot = 0x10001 # identification blob (detached signatures only)
cdTicketSlot = 0x10002 # ticket embedded in signature (DMG only)
# Apple custom OIDs used in SignerInfo
SEC_OID_APPLE_HASH_AGILITY = '1.2.840.113635.100.9.1'
SEC_OID_APPLE_HASH_AGILITY_V2 = '1.2.840.113635.100.9.2'
SEC_OID_APPLE_EXPIRATION_TIME = '1.2.840.113635.100.9.3'
m = macholib.MachO.MachO(sys.argv[1])
h = m.headers[0]
sigmeta = [cmd for cmd in h.commands if cmd[0].cmd == LC_CODE_SIGNATURE]
sigmeta = sigmeta[0]
with open(sys.argv[1], 'rb') as f:
f.seek(sigmeta[1].dataoff)
sig = f.read(sigmeta[1].datasize)
with io.BytesIO(sig) as f:
hdr = struct.unpack('>II', f.read(8))
assert(hdr[0] == 0xfade0cc0)
num = struct.unpack('>I', f.read(4))[0]
slots = []
for slot in range(num):
(slot_id, offset) = struct.unpack('>II', f.read(8))
slots.append((slot_id, offset))
blobs = []
for (slot_id, offset) in slots:
f.seek(offset)
(blob_id, blob_size) = struct.unpack('>II', f.read(8))
blob_data = f.read(blob_size)
blobs.append((slot_id, blob_id, blob_data))
def sort_attributes(attrs_in):
'''
Sort the authenticated attributes for signing by re-encoding them, asn1crypto
takes care of the actual sorting of the set.
'''
attrs_out = CMSAttributes()
for attrval in attrs_in:
attrs_out.append(attrval)
return attrs_out
ctx = ValidationContext(trust_roots=[APPLE_ROOT], allow_fetching=False)
validate_chain = True
for (slot_id, blob_id, blob_data) in blobs:
if slot_id == cdSignatureSlot:
content = ContentInfo.load(blob_data)
sd = content['content']
assert(isinstance(sd, SignedData))
print('version', sd['version'].native)
print('digest_algorithms', [a.native for a in sd['digest_algorithms']])
print('encap_content_info', sd['encap_content_info'].native)
# Parse certificates.
certs = []
for cert in sd['certificates']:
c = cert.chosen
assert(isinstance(c, asn1crypto.x509.Certificate))
certs.append(c)
intermediates = certs[0:-1]
end_entity_cert = certs[-1]
# this only works after adding
# '1.2.840.113635.100.6.1.13', # devid_execute
# to supported_extensions in certvalidator/validate.py
if validate_chain:
validator = certvalidator.CertificateValidator(end_entity_cert, intermediates, ctx)
validator.validate_usage({'digital_signature'}, {'code_signing'})
# Validate SignerInfos
# Inspired by https://github.com/ralphje/signify/blob/master/signify/signerinfo.py
public_key = asymmetric.load_public_key(end_entity_cert.public_key)
for signerinfo in sd['signer_infos']:
assert(isinstance(signerinfo, asn1crypto.cms.SignerInfo))
data = sort_attributes(signerinfo['signed_attrs']).dump()
signature = signerinfo['signature'].contents
digest_algorithm = signerinfo['digest_algorithm']['algorithm'].native
signature_algorithm = signerinfo['signature_algorithm']['algorithm'].native
assert(signature_algorithm == 'rsassa_pkcs1v15')
# raises oscrypto.errors.SignatureError on wrong signature
asymmetric.rsa_pkcs1v15_verify(public_key, signature, data, digest_algorithm)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment