Skip to content
This repository has been archived by the owner on Feb 13, 2020. It is now read-only.

sha256 signing support #72

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 86 additions & 88 deletions isign/codesig.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,19 @@ class CodeDirectorySlot(object):
def __init__(self, codesig):
self.codesig = codesig

def get_hash(self):
return hashlib.sha1(self.get_contents()).digest()
def get_hash(self, hash_algorithm):
if hash_algorithm == "sha1":
return hashlib.sha1(self.get_contents()).digest()
elif hash_algorithm == "sha256":
return hashlib.sha256(self.get_contents()).digest()


class EntitlementsSlot(CodeDirectorySlot):
offset = -5

def get_contents(self):
return self.codesig.get_blob_data('CSMAGIC_ENTITLEMENT')
blobs = self.codesig.get_blobs('CSMAGIC_ENTITLEMENT', min_expected=1, max_expected=1)
return self.codesig.get_blob_data(blobs[0])


class ApplicationSlot(CodeDirectorySlot):
Expand All @@ -48,7 +52,8 @@ class RequirementsSlot(CodeDirectorySlot):
offset = -2

def get_contents(self):
return self.codesig.get_blob_data('CSMAGIC_REQUIREMENTS')
blobs = self.codesig.get_blobs('CSMAGIC_REQUIREMENTS', min_expected=1, max_expected=1)
return self.codesig.get_blob_data(blobs[0])


class InfoSlot(CodeDirectorySlot):
Expand Down Expand Up @@ -76,38 +81,52 @@ def is_sha256_signature(self):
def build_data(self):
return macho_cs.Blob.build(self.construct)

def get_blob(self, magic):
def get_blobs(self, magic, min_expected=None, max_expected=None):
""" get the blobs corresponding to the magic value from the blob index """
blobs = []
for index in self.construct.data.BlobIndex:
if index.blob.magic == magic:
return index.blob
raise KeyError(magic)
blobs.append(index.blob)

if min_expected != None and len(blobs) < min_expected:
raise KeyError("""The number of slots in blob index for magic '{}' was less than
the minimum expected ({})""".format(magic, min_expected))

if max_expected != None and len(blobs) > max_expected:
raise KeyError("""The number of slots in blob index for magic '{}' was more than
the maximum expected ({})""".format(magic, max_expected))


def get_blob_data(self, magic):
return blobs

def get_blob_data(self, blob):
""" convenience method, if we just want the data """
blob = self.get_blob(magic)
return macho_cs.Blob_.build(blob)

def set_entitlements(self, entitlements_path):
# log.debug("entitlements:")
try:
entitlements = self.get_blob('CSMAGIC_ENTITLEMENT')
entitlements_blobs = self.get_blobs('CSMAGIC_ENTITLEMENT', min_expected=1, max_expected=1)
entitlements = entitlements_blobs[0]
# log.debug("found entitlements slot in the image")
except KeyError:
log.debug("no entitlements found")
# log.debug("no entitlements found")
pass
else:
# make entitlements data if slot was found
# libraries do not have entitlements data
# so this is actually a difference between libs and apps
# entitlements_data = macho_cs.Blob_.build(entitlements)
# log.debug(hashlib.sha1(entitlements_data).hexdigest())

log.debug("using entitlements at path: {}".format(entitlements_path))
entitlements.bytes = open(entitlements_path, "rb").read()
entitlements.length = len(entitlements.bytes) + 8
# entitlements_data = macho_cs.Blob_.build(entitlements)
# log.debug(hashlib.sha1(entitlements_data).hexdigest())

def set_requirements(self, signer):
# log.debug("requirements:")
requirements = self.get_blob('CSMAGIC_REQUIREMENTS')
requirements_blobs = self.get_blobs('CSMAGIC_REQUIREMENTS', min_expected=1, max_expected=1)
requirements = requirements_blobs[0]
# requirements_data = macho_cs.Blob_.build(requirements)
# log.debug(hashlib.sha1(requirements_data).hexdigest())

Expand Down Expand Up @@ -158,75 +177,81 @@ def set_requirements(self, signer):
# requirements_data = macho_cs.Blob_.build(requirements)
# log.debug(hashlib.sha1(requirements_data).hexdigest())

def get_codedirectory(self):
return self.get_blob('CSMAGIC_CODEDIRECTORY')

def get_codedirectory_hash_index(self, slot):
def get_codedirectory_hash_index(self, slot, code_directory):
""" The slots have negative offsets, because they start from the 'top'.
So to get the actual index, we add it to the length of the
slots. """
return slot.offset + self.get_codedirectory().data.nSpecialSlots
return slot.offset + code_directory.data.nSpecialSlots

def has_codedirectory_slot(self, slot):
def has_codedirectory_slot(self, slot, code_directory):
""" Some dylibs have all 5 slots, even though technically they only need
the first 2. If this dylib only has 2 slots, some of the calculated
indices for slots will be negative. This means we don't do
those slots when resigning (for dylibs, they don't add any
security anyway) """
return self.get_codedirectory_hash_index(slot) >= 0
return self.get_codedirectory_hash_index(slot, code_directory) >= 0

def fill_codedirectory_slot(self, slot):
def fill_codedirectory_slot(self, slot, code_directory, hash_algorithm):
if self.signable.should_fill_slot(self, slot):
index = self.get_codedirectory_hash_index(slot)
self.get_codedirectory().data.hashes[index] = slot.get_hash()
index = self.get_codedirectory_hash_index(slot, code_directory)
code_directory.data.hashes[index] = slot.get_hash(hash_algorithm)

def set_codedirectory(self, seal_path, info_path, signer):
if self.has_codedirectory_slot(EntitlementsSlot):
self.fill_codedirectory_slot(EntitlementsSlot(self))
def set_codedirectories(self, seal_path, info_path, signer):
cd = self.get_blobs('CSMAGIC_CODEDIRECTORY', min_expected=1, max_expected=2)
changed_bundle_id = self.signable.get_changed_bundle_id()

if self.has_codedirectory_slot(ResourceDirSlot):
self.fill_codedirectory_slot(ResourceDirSlot(seal_path))
for i, code_directory in enumerate(cd):
# TODO: Is there a better way to figure out which hashing algorithm we should use?
hash_algorithm = 'sha256' if i > 0 else 'sha1'

if self.has_codedirectory_slot(RequirementsSlot):
self.fill_codedirectory_slot(RequirementsSlot(self))
if self.has_codedirectory_slot(EntitlementsSlot, code_directory):
self.fill_codedirectory_slot(EntitlementsSlot(self), code_directory, hash_algorithm)

if self.has_codedirectory_slot(ApplicationSlot):
self.fill_codedirectory_slot(ApplicationSlot(self))
if self.has_codedirectory_slot(ResourceDirSlot, code_directory):
self.fill_codedirectory_slot(ResourceDirSlot(seal_path), code_directory, hash_algorithm)

if self.has_codedirectory_slot(InfoSlot):
self.fill_codedirectory_slot(InfoSlot(info_path))
if self.has_codedirectory_slot(RequirementsSlot, code_directory):
self.fill_codedirectory_slot(RequirementsSlot(self), code_directory, hash_algorithm)

cd = self.get_codedirectory()
cd.data.teamID = signer.team_id

changed_bundle_id = self.signable.get_changed_bundle_id()
if changed_bundle_id:
offset_change = len(changed_bundle_id) - len(cd.data.ident)
cd.data.ident = changed_bundle_id
cd.data.hashOffset += offset_change
if cd.data.teamIDOffset == None:
cd.data.teamIDOffset = offset_change
else:
cd.data.teamIDOffset += offset_change
cd.length += offset_change

cd.bytes = macho_cs.CodeDirectory.build(cd.data)
# cd_data = macho_cs.Blob_.build(cd)
# log.debug(len(cd_data))
# open("cdrip", "wb").write(cd_data)
# log.debug("CDHash:" + hashlib.sha1(cd_data).hexdigest())
if self.has_codedirectory_slot(ApplicationSlot, code_directory):
self.fill_codedirectory_slot(ApplicationSlot(self), code_directory, hash_algorithm)

if self.has_codedirectory_slot(InfoSlot, code_directory):
self.fill_codedirectory_slot(InfoSlot(info_path), code_directory, hash_algorithm)

code_directory.data.teamID = signer.team_id

if changed_bundle_id:
offset_change = len(changed_bundle_id) - len(code_directory.data.ident)
code_directory.data.ident = changed_bundle_id
code_directory.data.hashOffset += offset_change
if code_directory.data.teamIDOffset == None:
code_directory.data.teamIDOffset = offset_change
else:
code_directory.data.teamIDOffset += offset_change
code_directory.length += offset_change

code_directory.bytes = macho_cs.CodeDirectory.build(code_directory.data)
# cd_data = macho_cs.Blob_.build(cd)
# log.debug(len(cd_data))
# open("cdrip", "wb").write(cd_data)
# log.debug("CDHash:" + hashlib.sha1(cd_data).hexdigest())

def set_signature(self, signer):
# TODO how do we even know this blobwrapper contains the signature?
# seems like this is a coincidence of the structure, where
# it's the only blobwrapper at that level...
# log.debug("sig:")
sigwrapper = self.get_blob('CSMAGIC_BLOBWRAPPER')
blob_wrappers = self.get_blobs('CSMAGIC_BLOBWRAPPER', min_expected=1, max_expected=1)
sigwrapper = blob_wrappers[0]

# oldsig = sigwrapper.bytes.value
# signer._log_parsed_asn1(sigwrapper.data.data.value)
# open("sigrip.der", "wb").write(sigwrapper.data.data.value)
cd_data = self.get_blob_data('CSMAGIC_CODEDIRECTORY')
sig = signer.sign(cd_data)

code_directories = self.get_blobs('CSMAGIC_CODEDIRECTORY', min_expected=1, max_expected=2)
cd_data = self.get_blob_data(code_directories[0])
sig = signer.sign(cd_data, 'sha1')
# log.debug("sig len: {0}".format(len(sig)))
# log.debug("old sig len: {0}".format(len(oldsig)))
# open("my_sigrip.der", "wb").write(sig)
Expand All @@ -242,7 +267,8 @@ def update_offsets(self):
offset = self.construct.data.BlobIndex[0].offset
for blob in self.construct.data.BlobIndex:
blob.offset = offset
offset += len(macho_cs.Blob.build(blob.blob))
blob_data = macho_cs.Blob.build(blob.blob)
offset += len(blob_data)

superblob = macho_cs.SuperBlob.build(self.construct.data)
self.construct.length = len(superblob) + 8
Expand All @@ -251,35 +277,7 @@ def update_offsets(self):
def resign(self, bundle, signer):
""" Do the actual signing. Create the structre and then update all the
byte offsets """
if self.is_sha256_signature():
# Might be an app signed from Xcode 7.3+ with sha256 stuff
codedirs = []
for i, index in enumerate(self.construct.data.BlobIndex):
if index.blob.magic == 'CSMAGIC_CODEDIRECTORY':
codedirs.append(i)

if len(codedirs) == 2:
# Remove the sha256 code directory
i = codedirs.pop()
if (len(self.construct.data.BlobIndex) <= i + 1 or
self.construct.data.BlobIndex[i + 1].blob.magic != 'CSMAGIC_BLOBWRAPPER'):
# There's no following blobwrapper
raise Exception("Could not find blob wrapper!")

del self.construct.data.BlobIndex[i]
removed = 1
# CSMAGIC_BLOBWRAPPER is now at index i

# Remove any previous CSMAGIC_BLOBWRAPPERs, the last one is at the expected position
for j in reversed(xrange(i)):
if self.construct.data.BlobIndex[j].blob.magic == 'CSMAGIC_BLOBWRAPPER':
del self.construct.data.BlobIndex[j]
removed += 1

self.construct.data.count -= removed

elif len(codedirs) > 2:
raise Exception("Too many code directories (%d)" % len(codedirs))
codedirs = self.get_blobs('CSMAGIC_CODEDIRECTORY', min_expected=1, max_expected=2)

# TODO - the hasattr is a code smell. Make entitlements dependent on
# isinstance(App, bundle) or signable type being Executable? May need to do
Expand All @@ -288,7 +286,7 @@ def resign(self, bundle, signer):
self.set_entitlements(bundle.entitlements_path)
self.set_requirements(signer)
# See docs/codedirectory.rst for some notes on optional hashes
self.set_codedirectory(bundle.seal_path, bundle.info_path, signer)
self.set_codedirectories(bundle.seal_path, bundle.info_path, signer)
self.set_signature(signer)
self.update_offsets()

Expand Down
5 changes: 3 additions & 2 deletions isign/signer.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ def check_openssl_version(self):
msg = "Signing may not work: OpenSSL version is {0}, need {1} !"
log.warn(msg.format(openssl_version, MINIMUM_OPENSSL_VERSION))

def sign(self, data):
def sign(self, data, digest_algorithm = "sha1"):
""" sign data, return filehandle """
cmd = [
"cms",
Expand All @@ -107,7 +107,8 @@ def sign(self, data):
"-signer", self.signer_cert_file,
"-inkey", self.signer_key_file,
"-keyform", "pem",
"-outform", "DER"
"-outform", "DER",
"-md", digest_algorithm
]
signature = openssl_command(cmd, data)
# in some cases we've seen this return a zero length file.
Expand Down