def dmg_signpackage(pkgfile, dstfile, keychain, mac_id, subject_ou, fake=False, passphrase=None): """ Sign a mac build, putting results into `dstfile`. pkgfile must be a tar, which gets unpacked, signed, and repacked. """ # Keep track of our output in a list here, and we can output everything # when we're done This is to avoid interleaving the output from # multiple processes. # TODO: Is it even possible to do 'fake' signing? logs = [] logs.append("Repacking %s to %s" % (pkgfile, dstfile)) tmpdir = tempfile.mkdtemp() pkgdir = os.path.dirname(pkgfile) try: # Unpack it logs.append("Unpacking %s to %s" % (pkgfile, tmpdir)) unpacktar(pkgfile, tmpdir) for macdir in os.listdir(tmpdir): macdir = os.path.join(tmpdir, macdir) log.debug('Checking if we should sign %s', macdir) if shouldSign(macdir, 'mac'): log.debug('Signing %s', macdir) # Grab the code resources file. Need to find the filename code_resources = macdir + \ "/Contents/_CodeSignature/CodeResources" lockfile = os.path.join(os.path.dirname(keychain), '.lock') dmg_signfile(macdir, keychain, mac_id, code_resources, get_identifier(macdir), subject_ou, lockfile, passphrase=passphrase) # Repack it logs.append("Packing %s" % dstfile) tar_dir(dstfile, tmpdir) except: log.exception("Error signing %s", pkgfile) return False finally: # Clean up after ourselves, and output our logs shutil.rmtree(tmpdir) log.info("\n ".join(logs))
def dmg_signpackage(pkgfile, dstfile, keychain, mac_id, subject_ou, fake=False, passphrase=None): """ Sign a mac build, putting results into `dstfile`. pkgfile must be a tar, which gets unpacked, signed, and repacked. """ # Keep track of our output in a list here, and we can output everything # when we're done This is to avoid interleaving the output from # multiple processes. from flufl.lock import Lock, TimeOutError, NotLockedError from datetime import timedelta import pexpect # TODO: Is it even possible to do 'fake' signing? logs = [] logs.append("Repacking %s to %s" % (pkgfile, dstfile)) # pexpect requires a string as input unlock_command = 'security unlock-keychain ' + keychain lock_command = ['security', 'lock-keychain', keychain] lockfile = os.path.join(os.path.dirname(keychain), '.lock') tmpdir = tempfile.mkdtemp() try: # Unpack it logs.append("Unpacking %s to %s" % (pkgfile, tmpdir)) unpacktar(pkgfile, tmpdir) for macdir in os.listdir(tmpdir): macdir = os.path.join(tmpdir, macdir) log.debug('Checking if we should sign %s', macdir) if shouldSign(macdir, 'mac'): log.debug('Need to sign %s', macdir) try: sign_lock = None # Acquire a lock for the signing command, to ensure we don't have a # race condition where one process locks the keychain immediately after another # unlocks it. log.debug("Try to acquire %s", lockfile) sign_lock = Lock(lockfile) # Put a 30 second timeout on waiting for the lock. sign_lock.lock(timedelta(0, 30)) # Unlock the keychain so that we do not get a user-interaction prompt to use # the keychain for signing. This operation requires a password. child = pexpect.spawn(unlock_command) child.expect('password to unlock .*') child.sendline(passphrase) # read output until child exits child.read() child.close() if child.exitstatus != 0: raise ValueError("keychain unlock failed") # Sign the thing! dmg_signfile(macdir, keychain, mac_id, subject_ou, fake) except TimeOutError: # timed out acquiring lock, give an error log.exception( "Timeout acquiring lock %s for codesign, is something broken? ", lockfile) raise except: # catch any other locking error log.exception( "Error acquiring %s for codesign, is something broken?", lockfile) raise finally: # Lock the keychain again, no matter what happens # This command does not require a password check_call(lock_command) # Release the lock, if it was acquired if sign_lock: try: sign_lock.unlock() log.debug("Release %s", lockfile) except NotLockedError: log.debug("%s was already unlocked", lockfile) # Repack it logs.append("Packing %s" % dstfile) tar_dir(dstfile, tmpdir) except: log.exception("Error signing %s", pkgfile) raise finally: # Clean up after ourselves, and output our logs shutil.rmtree(tmpdir) log.info("\n ".join(logs))
def dmg_signpackage(pkgfile, dstfile, keychain, mac_id, subject_ou, fake=False, passphrase=None): """ Sign a mac build, putting results into `dstfile`. pkgfile must be a tar, which gets unpacked, signed, and repacked. """ # Keep track of our output in a list here, and we can output everything # when we're done This is to avoid interleaving the output from # multiple processes. from flufl.lock import Lock, TimeOutError, NotLockedError from datetime import timedelta import pexpect # TODO: Is it even possible to do 'fake' signing? logs = [] logs.append("Repacking %s to %s" % (pkgfile, dstfile)) # pexpect requires a string as input unlock_command = 'security unlock-keychain ' + keychain lock_command = ['security', 'lock-keychain', keychain] lockfile = os.path.join(os.path.dirname(keychain), '.lock') tmpdir = tempfile.mkdtemp() try: # Unpack it logs.append("Unpacking %s to %s" % (pkgfile, tmpdir)) unpacktar(pkgfile, tmpdir) for macdir in os.listdir(tmpdir): macdir = os.path.join(tmpdir, macdir) log.debug('Checking if we should sign %s', macdir) if shouldSign(macdir, 'mac'): log.debug('Need to sign %s', macdir) try: sign_lock = None # Acquire a lock for the signing command, to ensure we don't have a # race condition where one process locks the keychain immediately after another # unlocks it. log.debug("Try to acquire %s", lockfile) sign_lock = Lock(lockfile) # Put a 30 second timeout on waiting for the lock. sign_lock.lock(timedelta(0, 30)) # Unlock the keychain so that we do not get a user-interaction prompt to use # the keychain for signing. This operation requires a password. child = pexpect.spawn(unlock_command) child.expect('password to unlock .*') child.sendline(passphrase) # read output until child exits child.read() child.close() if child.exitstatus != 0: raise ValueError("keychain unlock failed") # Sign the thing! dmg_signfile(macdir, keychain, mac_id, subject_ou, fake) except TimeOutError: # timed out acquiring lock, give an error log.exception("Timeout acquiring lock %s for codesign, is something broken? ", lockfile) raise except: # catch any other locking error log.exception("Error acquiring %s for codesign, is something broken?", lockfile) raise finally: # Lock the keychain again, no matter what happens # This command does not require a password check_call(lock_command) # Release the lock, if it was acquired if sign_lock: try: sign_lock.unlock() log.debug("Release %s", lockfile) except NotLockedError: log.debug("%s was already unlocked", lockfile) # Repack it logs.append("Packing %s" % dstfile) tar_dir(dstfile, tmpdir) except: log.exception("Error signing %s", pkgfile) raise finally: # Clean up after ourselves, and output our logs shutil.rmtree(tmpdir) log.info("\n ".join(logs))