Repository: apperian/isign Branch: develop Commit: e5c71c645638 Files: 191 Total size: 440.0 KB Directory structure: gitextract_wnigoa8g/ ├── .gitignore ├── CONDUCT.md ├── INSTALL.sh ├── LICENSE.txt ├── MANIFEST.in ├── PKG-INFO ├── PREREQUISITES.rst ├── README.rst ├── apple/ │ ├── README.rst │ ├── provisions.py │ └── provisions.sh ├── bin/ │ ├── isign │ ├── isign_export_creds.sh │ ├── isign_guess_mobileprovision.sh │ ├── make_seal │ ├── multisign │ └── pprint_codesig ├── dev/ │ ├── requirements.txt │ └── setup.sh ├── docs/ │ ├── applecerts.rst │ ├── codedirectory.rst │ ├── credentials.rst │ └── speed.rst ├── isign/ │ ├── __init__.py │ ├── apple_credentials/ │ │ └── README.rst │ ├── archive.py │ ├── bundle.py │ ├── code_resources.py │ ├── code_resources_template.xml │ ├── codesig.py │ ├── der_encoder.py │ ├── exceptions.py │ ├── isign.py │ ├── macho.py │ ├── macho_cs.py │ ├── makesig.py │ ├── multisign.py │ ├── signable.py │ ├── signer.py │ └── utils.py ├── jenkins.sh ├── run_tests.sh ├── setup.cfg ├── setup.py ├── tests/ │ ├── NotAnApp.ipa │ ├── NotAnApp.txt │ ├── NotAnAppDir/ │ │ └── README.md │ ├── README.md │ ├── Test.app/ │ │ ├── Assets.car │ │ ├── Base.lproj/ │ │ │ ├── LaunchScreen.storyboardc/ │ │ │ │ ├── 01J-lp-oVM-view-Ze5-6b-2t3.nib │ │ │ │ ├── Info.plist │ │ │ │ └── UIViewController-01J-lp-oVM.nib │ │ │ └── Main.storyboardc/ │ │ │ ├── 8rJ-Kc-sve-view-QS5-Rx-YEW.nib │ │ │ ├── 9pv-A4-QxB-view-tsR-hK-woN.nib │ │ │ ├── Info.plist │ │ │ └── UITabBarController-49e-Tb-3d3.nib │ │ ├── Info.plist │ │ ├── PkgInfo │ │ ├── _CodeSignature/ │ │ │ └── CodeResources │ │ ├── build.sh │ │ ├── isignTestApp │ │ ├── isignTestApp.xcodeproj/ │ │ │ ├── project.pbxproj │ │ │ ├── project.xcworkspace/ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ └── xcuserdata/ │ │ │ │ └── neilk.xcuserdatad/ │ │ │ │ └── UserInterfaceState.xcuserstate │ │ │ └── xcuserdata/ │ │ │ └── neilk.xcuserdatad/ │ │ │ └── xcschemes/ │ │ │ ├── isignTestApp.xcscheme │ │ │ └── xcschememanagement.plist │ │ ├── isignTestAppTests/ │ │ │ ├── Info.plist │ │ │ └── isignTestAppTests.swift │ │ └── isignTestAppUITests/ │ │ ├── Info.plist │ │ └── isignTestAppUITests.swift │ ├── Test.app.codesig.construct.txt │ ├── Test.ipa │ ├── TestWithFrameworks.ipa │ ├── Test_unsigned_fat.app/ │ │ ├── Assets.car │ │ ├── Base.lproj/ │ │ │ ├── LaunchScreen.storyboardc/ │ │ │ │ ├── 01J-lp-oVM-view-Ze5-6b-2t3.nib │ │ │ │ ├── Info.plist │ │ │ │ └── UIViewController-01J-lp-oVM.nib │ │ │ └── Main.storyboardc/ │ │ │ ├── 8rJ-Kc-sve-view-QS5-Rx-YEW.nib │ │ │ ├── 9pv-A4-QxB-view-tsR-hK-woN.nib │ │ │ ├── Info.plist │ │ │ └── UITabBarController-49e-Tb-3d3.nib │ │ ├── Info.plist │ │ ├── PkgInfo │ │ ├── build.sh │ │ ├── isignTestApp │ │ ├── isignTestApp.xcodeproj/ │ │ │ ├── project.pbxproj │ │ │ ├── project.xcworkspace/ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ └── xcuserdata/ │ │ │ │ └── neilk.xcuserdatad/ │ │ │ │ └── UserInterfaceState.xcuserstate │ │ │ └── xcuserdata/ │ │ │ └── neilk.xcuserdatad/ │ │ │ └── xcschemes/ │ │ │ ├── isignTestApp.xcscheme │ │ │ └── xcschememanagement.plist │ │ ├── isignTestAppTests/ │ │ │ ├── Info.plist │ │ │ └── isignTestAppTests.swift │ │ └── isignTestAppUITests/ │ │ ├── Info.plist │ │ └── isignTestAppUITests.swift │ ├── Test_unsigned_thin.app/ │ │ ├── Assets.car │ │ ├── Base.lproj/ │ │ │ ├── LaunchScreen.storyboardc/ │ │ │ │ ├── 01J-lp-oVM-view-Ze5-6b-2t3.nib │ │ │ │ ├── Info.plist │ │ │ │ └── UIViewController-01J-lp-oVM.nib │ │ │ └── Main.storyboardc/ │ │ │ ├── 8rJ-Kc-sve-view-QS5-Rx-YEW.nib │ │ │ ├── 9pv-A4-QxB-view-tsR-hK-woN.nib │ │ │ ├── Info.plist │ │ │ └── UITabBarController-49e-Tb-3d3.nib │ │ ├── Info.plist │ │ ├── PkgInfo │ │ ├── build.sh │ │ ├── isignTestApp │ │ ├── isignTestApp.xcodeproj/ │ │ │ ├── project.pbxproj │ │ │ ├── project.xcworkspace/ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ └── xcuserdata/ │ │ │ │ └── neilk.xcuserdatad/ │ │ │ │ └── UserInterfaceState.xcuserstate │ │ │ └── xcuserdata/ │ │ │ └── neilk.xcuserdatad/ │ │ │ └── xcschemes/ │ │ │ ├── isignTestApp.xcscheme │ │ │ └── xcschememanagement.plist │ │ ├── isignTestAppTests/ │ │ │ ├── Info.plist │ │ │ └── isignTestAppTests.swift │ │ └── isignTestAppUITests/ │ │ ├── Info.plist │ │ └── isignTestAppUITests.swift │ ├── bad_openssl │ ├── credentials/ │ │ ├── README.rst │ │ ├── makeFakePprof.sh │ │ ├── test.cert.pem │ │ ├── test.key.pem │ │ ├── test.mobileprovision │ │ └── test.mobileprovision.plist │ ├── credentials_std_names/ │ │ ├── README.rst │ │ ├── certificate.pem │ │ └── isign.mobileprovision │ ├── credentials_std_names_2/ │ │ ├── README.rst │ │ ├── certificate.pem │ │ ├── isign.mobileprovision │ │ └── key.pem │ ├── generate_codesig_construct_txt.py │ ├── isignTestApp/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── build.sh │ │ ├── exportOptions.plist │ │ ├── isignTestApp/ │ │ │ ├── AppDelegate.swift │ │ │ ├── Assets.xcassets/ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── first.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ └── second.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── Base.lproj/ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ └── Main.storyboard │ │ │ ├── FirstViewController.swift │ │ │ ├── Info.plist │ │ │ └── SecondViewController.swift │ │ ├── isignTestApp.xcodeproj/ │ │ │ ├── project.pbxproj │ │ │ ├── project.xcworkspace/ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ └── xcuserdata/ │ │ │ │ └── neilk.xcuserdatad/ │ │ │ │ └── UserInterfaceState.xcuserstate │ │ │ └── xcuserdata/ │ │ │ └── neilk.xcuserdatad/ │ │ │ └── xcschemes/ │ │ │ ├── isignTestApp.xcscheme │ │ │ └── xcschememanagement.plist │ │ ├── isignTestAppTests/ │ │ │ ├── Info.plist │ │ │ └── isignTestAppTests.swift │ │ └── isignTestAppUITests/ │ │ ├── Info.plist │ │ └── isignTestAppUITests.swift │ ├── isignTestAppWithFrameworks/ │ │ ├── .gitignore │ │ ├── Podfile │ │ ├── README.md │ │ ├── build.sh │ │ ├── exportOptions.plist │ │ ├── isignTestApp/ │ │ │ ├── AppDelegate.swift │ │ │ ├── Assets.xcassets/ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── first.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ └── second.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── Base.lproj/ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ └── Main.storyboard │ │ │ ├── FirstViewController.swift │ │ │ ├── Info.plist │ │ │ └── SecondViewController.swift │ │ ├── isignTestApp.xcodeproj/ │ │ │ ├── project.pbxproj │ │ │ ├── project.xcworkspace/ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ └── xcuserdata/ │ │ │ │ └── neilk.xcuserdatad/ │ │ │ │ └── UserInterfaceState.xcuserstate │ │ │ └── xcuserdata/ │ │ │ └── neilk.xcuserdatad/ │ │ │ └── xcschemes/ │ │ │ ├── isignTestApp.xcscheme │ │ │ └── xcschememanagement.plist │ │ ├── isignTestApp.xcworkspace/ │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcuserdata/ │ │ │ └── neilk.xcuserdatad/ │ │ │ └── UserInterfaceState.xcuserstate │ │ ├── isignTestAppTests/ │ │ │ ├── Info.plist │ │ │ └── isignTestAppTests.swift │ │ └── isignTestAppUITests/ │ │ ├── Info.plist │ │ └── isignTestAppUITests.swift │ ├── isign_base_test.py │ ├── monitor_temp_file.py │ ├── sample-entitlements.plist │ ├── test_archive.py │ ├── test_creds_dir.py │ ├── test_entitlements.py │ ├── test_helpers.py │ ├── test_multisign.py │ ├── test_parsing.py │ ├── test_public_interface.py │ ├── test_sig.py │ ├── test_versioning.py │ └── test_versus_apple.py └── version.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # generic *~ *# *._* *.log *.log.* *.pid # apple .DS_Store *~.nib # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] # C extensions *.so # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .cache nosetests.xml coverage.xml # Translations *.mo *.pot # Sphinx documentation docs/_build/ # PyBuilder target/ # Development .*.sw[po] *.sublime-project *.sublime-workspace .project .pydevproject .ropeproject *.sass-cache .aws.env .codeintel .coverage .noseids .semantic.cache .settings .idea .realsync *.tm_build_errors tmtags *.iml # iresign version isign/version.json # iresign temp files Entitlements.plist stage out.app out.ipa # all credentials *.pem *.p12 *.mobileprovision # except these !apple_credentials/applecerts.pem !tests/credentials/test.key.pem !tests/credentials/test.cert.pem !tests/credentials/test.mobileprovision !tests/credentials_std_names/certificate.pem !tests/credentials_std_names/isign.mobileprovision !tests/credentials_std_names_2/key.pem !tests/credentials_std_names_2/certificate.pem !tests/credentials_std_names_2/isign.mobileprovision ================================================ FILE: CONDUCT.md ================================================ ## Code of Conduct ### What is this code of conduct for? `isign` is a piece of technology, but **the core of the `isign` community is the people in it**. As such, we are committed to providing a friendly, safe and welcoming environment for all, regardless of gender, gender identity and expression, sexual orientation, ability, physical appearance, body size, race, age, socioeconomic status, religion (or lack thereof), or other marginalized aspect of comunity members. We expect all members of the `isign` community to abide by this Code of Conduct whenever interacting in `isign` venues (pull requests, GitHub issues, 1-1 or group chat, meetups, conferences, etc...) ### Examples of inappropriate behavior Because we come from a variety of backgrounds, we don't want to assume that everyone has the same assumptions about what is and isn't appropriate. Here are some examples of inappropriate behavior that are incompatible with our community's ethos: * Spamming, trolling, intentionally disrupting conversations, or irrelevant solicitation or advertisement * Making demeaning or discriminatory comments * Making negative assumptions about someone's background, abilities, or intentions * Harassing or stalking individuals (online or in person) * Giving someone unwelcome sexual attention or making unwelcome physical contact (in the case of an IRL event) * Sharing sexual images or using sexually explicit language In general: treat others how you would like to be treated, were you in their place. Don't be a jerk. _Do_ ask questions. _Do_ keep conflicts productively focused on technical issues. _Do_ think before you speak; remember that what is perceived as a funny witticism in your group of friends might be hurtful or reinforce hurtful stereotypes in the context of our diverse online community. _Do_ remember that we are all people, not robots, and all equally deserving of sensitivity and respect. (If and when robots join our community, let's treat them with respect too!) ### What will organizers do about inappropriate behavior? If we notice you doing or saying something inappropriate, an organizer will explain why it's inappropriate and ask you to stop. We won't demonize or vilify you. But please do stop the inappropriate behavior so we can get back to writing and discussing code in a safe environment. If you have philosophical disagreements about what's actually inappropriate, please take them to a separate public or private conversation with an `isign` maintainer so we don't turn pull requests into an ethics debate. If you keep doing unacceptable things, we'll likely ban you, report you to GitHub, or take other appropriate action. ### What if I see or am subject to what feels like inappropriate behavior? Let us know! Please notify a community organizer as soon as possible. Full contact information is listed in the [Contact Info](#contact-info) section of this document. All communications will be kept strictly confidential, unless otherwise required by law. No issue will be considered too inconsequential or unimportant for us to have a conversation about. ### Contact Info If you need to report an incident, please contact any of the following organizers directly: * Neil Kandalgaonkar [email](mailto:neilk@neilk.net) [twitter](https://twitter.com/flipzagging/) * Jonathan Lipps [email](mailto:jlipps@saucelabs.com) [twitter](https://twitter.com/jlipps) ### Credit, License, and Attribution This Code of Conduct is distributed under a [Creative Commons Attribution-ShareAlike license](http://creativecommons.org/licenses/by-sa/3.0/). It's inspired among other things by: * [Citizen Code of Conduct](http://citizencodeofconduct.org/) * [npmjs](https://www.npmjs.com/policies/conduct) * [Geek Feminism](http://geekfeminism.wikia.com/wiki/Conference_anti-harassment/Policy) * [Ashe Dryden](http://www.ashedryden.com/blog/codes-of-conduct-101-faq) * [Model View Culture](https://modelviewculture.com/issues/events) * [Open Source & Feelings](http://osfeels.com/conduct). ================================================ FILE: INSTALL.sh ================================================ #!/bin/bash set -e REQUIRED_OPENSSL_VERSION="1.0.1" REQUIRED_PIP_VERSION="7.0.0" BREW_USER="unknown" DEVELOP=false if [[ "$1" == 'develop' ]]; then DEVELOP=true fi abort_install() { warn "Aborting the install. If you want to install prerequisites" warn "manually, see PREREQUISITES.rst." exit 0; } warn() { echo "$@" 1>&2; } trim() { # has the side effect of trimming whitespace echo "$@" | xargs } get_ok_to_upgrade() { warn "This script may install or upgrade the following software:" warn " - brew"; warn " - openssl"; warn " - libffi" warn " - python and pip"; warn " - libimobiledevice and related utilities"; read -p "Okay to continue? [Y/n]" -n 1 -r warn " " warn " " if [[ $REPLY =~ ^[Nn]$ ]]; then abort_install fi } # given arguments like 1.0.2e, 3, returns "2" get_version_part() { local version=$1 local part=$2 echo $version | cut -f $part -d '.' | sed 's/[^0-9]//g' } check_version() { local required=$1; local given=$2; local i for i in 1 2 3; do local required_part=$(get_version_part $required $i); local given_part=$(get_version_part $given $i); if [[ $given_part -lt $required_part ]]; then return 1; fi done; return 0; } exists() { # warn "checking for existence of $1" which $1 > /dev/null } openssl_version_ok() { local openssl_path=$1 warn "Trying to check if $openssl_path openssl version is okay..." if [[ -e $openssl_path ]]; then local openssl_version=$($openssl_path version | cut -f 2 -d ' '); warn "Found $openssl_path version $openssl_version." if check_version $REQUIRED_OPENSSL_VERSION $openssl_version; then warn "$openssl_path version looks okay." return 0; fi fi warn "$openssl_path version was not okay!" return 1; } setup_brew() { if exists brew; then warn "You seem to have brew." else # This is what https://brew.sh recommends... # Installing straight from a URL? What could go wrong? local homebrew_install_url="https://raw.githubusercontent.com/Homebrew/install/master/install" warn "We need to install brew..." warn "Okay to install via ruby script at this URL?" warn " $homebrew_install_url"; warn " " read -p "Okay to continue? [Y/n]" -n 1 -r warn " " warn " " if [[ $REPLY =~ ^[Nn]$ ]]; then abort_install fi ruby -e "$(curl -fsSL $homebrew_install_url)"; fi # which user owns the Cellar? We'll need this to install things... BREW_USER=$(stat -f '%Su' `brew --cellar`) warn "brew's stuff seems to be owned by $BREW_USER..." return 0; } # Commands that write to brew's Cellar need to not be root brew_write() { local command=$1 local package=$2 if [[ $EUID -eq 0 ]]; then sudo -u $BREW_USER brew $command $package else brew $command $package fi } # some brew packages have this in machine readable form # but not all :( brew_get_flags() { local package=$1 local flags=$2 brew info $package | grep $flags | awk '{print $2}' } # check if a program is managed by brew # it might be a symlink like /usr/local/bin/openssl, pointing to somewhere in the Cellar is_brew_program() { readlink `which $1` | grep `brew --prefix` >/dev/null; } mac_setup_openssl() { # warn "start mac_setup_openssl" # if the currently installed openssl doesn't meet our requirements, # install or upgrade with brew local openssl_path=$(which openssl) if [[ -n $openssl_path ]]; then if ! openssl_version_ok $openssl_path; then brew_setup_openssl fi fi # At this point, the openssl version is okay. It may or may not # be a brew installed program. If it is, set up some compilation # library paths. We append to $LDFLAGS and $CPPFLAGS because some other # things put flags in there too. if is_brew_program openssl; then openssl_ldflags=$(brew_get_flags openssl LDFLAGS) export LDFLAGS=$(trim "$LDFLAGS $openssl_ldflags") openssl_cppflags=$(brew_get_flags openssl CPPFLAGS) export CPPFLAGS=$(trim "$CPPFLAGS $openssl_cppflags") # Some stackoverflow answers use this too? export CFLAGS=$CPPFLAGS fi return 0; } brew_setup_openssl() { # So now we know there either wasn't an openssl, or it wasn't # a version good enough. Time for brew! # is the brew openssl installed? if not, install brew_openssl_path=$(brew list openssl | grep -e '/openssl$') # warn "brew openssl path = $brew_openssl_path" if [[ -z $brew_openssl_path ]]; then warn "Installing openssl..." brew_write install openssl brew_openssl_path=$(brew list openssl | grep -e '/openssl$') fi # warn "brew openssl path = $brew_openssl_path" # is the brew openssl the right version? if not, upgrade if ! openssl_version_ok $brew_openssl_path; then warn "Upgrading openssl..." brew_write upgrade openssl brew_openssl_path=$(brew list openssl | grep -e '/openssl$') fi # warn "okay by now $brew_openssl_path should be an acceptable openssl" # for various reasons, brew will refuse to simultaneously # be root and link the brew openssl binary somewhere useful. # So we do it manually. We assume that the brew --prefix will be # in the user's $PATH, and will take precedence over system openssl. brew_link_path=$(brew --prefix)/bin/openssl # warn "brew link path is $brew_link_path" if [[ -e brew_link_path ]]; then # warn "removing existing link" rm brew_link_path; fi warn "Linking $brew_openssl_path $brew_link_path." ln -s $brew_openssl_path $brew_link_path # let's see if we succeeded: the openssl in our path should now be ready! new_openssl_path=$(which openssl) # warn "new path is $new_openssl_path" if ! openssl_version_ok $new_openssl_path; then warn "We tried to install an openssl >$MINIMUM_OPENSSL_VERSION, but we failed." warn "Check if $brew_link_path is in your \$PATH ($PATH)." return 1 fi # warn "success, finally" return 0; } mac_setup_libffi() { warn "Checking for libffi..." if ! brew list libffi 2>/dev/null >/dev/null; then warn "Nope, no libffi. Installing..." brew_write install libffi fi # set up some compilation library paths (we'll need it later...) local libffi_ldflags=$(brew_get_flags libffi LDFLAGS) export LDFLAGS=$(trim "$LDFLAGS $libffi_ldflags") } mac_setup_python() { if exists pip; then pip_version=$(pip --version | awk '{ print $2 }') if check_version $REQUIRED_PIP_VERSION $pip_version; then warn "pip version $pip_version looks okay." return 0; fi fi brew_write install python } mac_setup_libimobiledevice() { if exists ideviceinstaller; then return 0; fi brew_write install libimobiledevice } mac_setup() { get_ok_to_upgrade setup_brew mac_setup_openssl mac_setup_libffi mac_setup_python mac_setup_libimobiledevice } linux_setup() { #apt-get install ideviceinstaller #apt-get install libimobiledevice-utils echo "Skipping apt-get installs, we use yum" } unamestr=`uname` if [[ "$unamestr" == 'Darwin' ]]; then mac_setup elif [[ "$unamestr" == 'Linux' ]]; then linux_setup else warn "Sorry, I don't know how to install on $unamestr."; exit 1; fi; echo "--- Flags ---" echo "LDFLAGS=$LDFLAGS" echo "CPPFLAGS=$CPPFLAGS" echo "CFLAGS=$CFLAGS" echo if [[ "$DEVELOP" == true ]]; then python setup.py develop else python setup.py install fi ================================================ FILE: LICENSE.txt ================================================ Copyright 2015 Sauce Labs Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: MANIFEST.in ================================================ include isign/version.json include isign/apple_credentials/applecerts.pem include isign/code_resources_template.xml ================================================ FILE: PKG-INFO ================================================ Name: isign Version: 1.6.16 ================================================ FILE: PREREQUISITES.rst ================================================ Prerequisites ============= For Linux or Mac platforms, the ``INSTALL.sh`` script should take care of everything you need. If you don't want to run that script on the machine where you want to re-sign apps, this will explain what you need. First, on the machine where you're going to re-sign apps, ensure `openssl `__ is at version 1.0.1 or better, like this: .. code:: $ openssl version OpenSSL 1.0.1 14 Mar 2012 If that looks okay, you can probably install. If not: .. _Linux: Linux ~~~~~ You can probably easily update this with your package manager, such as ``apt-get upgrade openssl``. .. _Mac OS X: Mac OS X ~~~~~~~~ With OS X 10.11 "El Capitan", Apple stopped shipping some programs, libraries, and headers that we'll need. You can use `homebrew `__ to install them: .. code:: $ brew install openssl libffi You will also have to put ``brew``'s openssl into your path somehow, probably like this: .. code:: $ brew list openssl ... /usr/local/Cellar/openssl/1.0.2e/bin/openssl <-- you want this ... $ ln -s /usr/local/Cellar/openssl/1.0.2e/bin/openssl /usr/local/bin/openssl If you really don't want to alter the default ``openssl``, you can put the path to brew's ``openssl`` in an environment variable, ``$OPENSSL``, e.g. .. code:: $ export OPENSSL=/usr/local/Cellar/openssl/1.0.2e/bin/openssl If ``isign`` sees that, it will use that for its ``openssl`` instead. Anyway, no matter how you install the binary, to complete the installation of ``isign`` you need to add some library paths to your environment. The procedure will look something like this. .. code:: $ brew info openssl ... build variables: LDFLAGS: -L/usr/local/opt/openssl/lib CPPFLAGS: -I/usr/local/opt/openssl/include $ brew info libffi ... build variables: LDFLAGS: -L/usr/local/opt/libffi/lib Then, take the flags from above, and put them into appropriate environment variables: .. code:: $ export LDFLAGS="-L/usr/local/opt/openssl/lib -L/usr/local/opt/libffi/lib" $ export CPPFLAGS="-I/usr/local/opt/openssl/include" Finally, be aware that the ``python`` that ships with Mac OS X doesn't have the package manager ``pip``. You can probably use ``easy_install`` instead of ``pip``. Or, you can get a more up-to-date python with ``brew install python``. Now you're probably ready to install ``isign``. A simple ``pip install isign`` should succeed. ================================================ FILE: README.rst ================================================ isign ===== A tool and library to re-sign iOS applications, without proprietary Apple software. For example, an iOS app in development would probably only run on the developer's iPhone. ``isign`` can alter the app so that it can run on another developer's iPhone. Apple tools already exist to do this. But with ``isign``, now you can do this on operating systems like Linux. Table of contents ----------------- - `Installing`_ - `How to get started`_ - `How to use isign`_ - `isign command line arguments`_ - `Contributing`_ - `More documentation`_ - `Authors`_ .. _Installing: Installing ---------- Linux ~~~~~ The latest version of ``isign`` can be installed via `PyPi `__: .. code:: $ pip install isign Mac OS X ~~~~~~~~ On Mac OS X, there are a lot of prerequisites, so the ``pip`` method probably won't work. The easiest method is to use ``git`` to clone the `source code repository `__ and run the install script: .. code:: $ git clone https://github.com/saucelabs/isign.git $ cd isign $ sudo ./INSTALL.sh .. _How to get started: How to get started ------------------ All the libraries and tools that ``isign`` needs to run will work on both Linux and Mac OS X. However, you will need a Mac to export your Apple developer credentials. If you're like most iOS developers, credentials are confusing -- if so check out the `documentation on credentials `__ on Github. You should have a key and certificate in `Keychain Access `__, and a provisioning profile associated with that certificate, that you can use to sign iOS apps for one or more of your own iOS devices. In Keychain Access, open the *Certificates*. Find the certificate you use to sign apps. Right click on it and export the key as a ``.p12`` file, let's say ``Certificates.p12``. If Keychain asks you for a password to protect this file, just leave it blank. Next, let's extract the key and certificate you need, into a standard PEM format. .. code:: $ isign_export_creds.sh ~/Certificates.p12 If you get prompted for a password, just press ``Return``. By default, ``isign_export_creds.sh`` will put these files into ``~/.isign``, which is the standard place to put ``isign`` configuration files. Finally, you need a provisioning profile from the Apple Developer Portal that uses the same certificate. If you've never dealt with this, the provisioning profile is what tells the phone that you Apple has okayed you installing apps onto this particular phone. If you develop with XCode, you might have a provisioning profile already. On the Mac where you develop with XCode, try running the ``isign_guess_mobileprovision.sh`` script. If you typically have only a few provisioning profiles and install on one phone, it might find it. Anyway, once you have a ``.mobileprovision`` file, move it to ``~/.isign/isign.mobileprovision``. The end result should look like this: .. code:: $ ls -l ~/.isign -r--r--r-- 1 alice staff 2377 Sep 4 14:17 certificate.pem -r--r--r-- 1 alice staff 9770 Nov 23 13:30 isign.mobileprovision -r-------- 1 alice staff 1846 Sep 4 14:17 key.pem And now you're ready to start re-signing apps! .. _How to use isign: How to use isign ---------------- If you've installed all the files in the proper locations above, then ``isign`` can be now invoked on any iOS ``.app`` directory, or ``.ipa`` archive, or ``.app.zip`` zipped directory. For example: .. code:: $ isign -o resigned.ipa my.ipa archived Ipa to /home/alice/resigned.ipa You can also call it from Python: .. code:: python from isign import isign isign.resign("my.ipa", output_path="resigned.ipa") .. _isign command line arguments: isign command line arguments ---------------------------- .. code:: # Resigning by specifying all credentials, input file, and output file $ isign -c /path/to/mycert.pem -k ~/mykey.pem -p path/to/my.mobileprovision \ -o resigned.ipa original.ipa # Resigning, with credentials under default filenames in ~/.isign - less to type! $ isign -o resigned.ipa original.ipa # Modify Info.plist properties in resigned app $ isign -i CFBundleIdentifier=com.example.myapp,CFBundleName=MyApp -o resigned.ipa original.ipa # Display Info.plist properties from an app as JSON $ isign -d my.ipa # Get help $ isign -h **-a , --apple-cert ** Path to Apple certificate in PEM format. This is already included in the library, so you will likely never need it. In the event that the certificates need to be changed, See the `Apple Certificate documentation `__. **-c , --certificate ** Path to your certificate in PEM format. Defaults to ``$HOME/.isign/certificate.pem``. **-d, --display** For the application path, display the information property list (Info.plist) as JSON. **-h, --help** Show a help message and exit. **-i, --info** While resigning, add or update info in the application's information property list (Info.plist). Takes a comma-separated list of key=value pairs, such as ``CFBundleIdentifier=com.example.app,CFBundleName=ExampleApp``. Use with caution! See Apple documentation for `valid Info.plist keys `_. **-k , --key ** Path to your private key in PEM format. Defaults to ``$HOME/.isign/key.pem``. **-n , --credentials ** Equivalent to: .. code:: -k /key.pem -c /certificate.pem -p /isign.mobileprovision **-o , --output ** Path to write the re-signed application. Defaults to ``out`` in your current working directory. **-p , --provisioning-profile ** Path to your provisioning profile. This should be associated with your certificate. Defaults to ``$HOME/.isign/isign.mobileprovision``. **-v, --verbose** More verbose logs will be printed to STDERR. **Application path** The app to be resigned is specified on the command line after other arguments. The application path is typically an IPA, but can also be a ``.app`` directory or even a zipped ``.app`` directory. When resigning, ``isign`` will always create an archive of the same type as the original. .. _Contributing: Contributing ------------ Sauce Labs open source projects have a `Code of Conduct `__. In short, we try to respect each other, listen, and be helpful. Development happens on `our Github repository `__. File an issue, or fork the code! You'll probably want to create some kind of python virtualenv, so you don't have to touch your system python or its libraries. `virtualenvwrapper `__ is a good tool for this. Then, just do the following: .. code:: $ git clone https://github.com/saucelabs/isign.git $ cd isign $ dev/setup.sh $ ./run_tests.sh If the tests don't pass please `file an issue `__. Please keep the tests up to date as you develop. Note: some tests require Apple's `codesign `__ to run, so they are skipped unless you run them on a Macintosh computer with developer tools. Okay, if all the tests passed, you now have an 'editable' install of isign. Any edits to this repo will affect (for instance) how the `isign` command line tool works. Sauce Labs supports ongoing public ``isign`` development. ``isign`` is a part of our infrastructure for the `iOS Real Device Cloud `__, which allows customers to test apps and websites on real iOS devices. ``isign`` has been successfully re-signing submitted customer apps in production since June 2015. .. _More documentation: More documentation ------------------ See the `docs `__ directory of this repository for random stuff that didn't fit here. .. _Authors: Authors ------- `Neil Kandalgaonkar `__ is the main developer and maintainer. Proof of concept by `Steven Hazel `__ and Neil Kandalgaonkar. Reference scripts using Apple tools by `Michael Han `__. ================================================ FILE: apple/README.rst ================================================ These scripts will re-provision (aka resign, code sign, etc) an app, using Apple tools. The isign tools emulate what these do, on Linux. provisions.sh is the original script that Mike Han (@mikehan) wrote. provisions.py is a python port. ================================================ FILE: apple/provisions.py ================================================ #!/usr/bin/env python # Port of @mikehan's provisions.sh import argparse import glob import os import os.path import shutil from subprocess import call, Popen import tempfile CODESIGN_BIN = '/usr/bin/codesign' PLIST_BUDDY_BIN = '/usr/libexec/PlistBuddy' SECURITY_BIN = '/usr/bin/security' ZIP_BIN = '/usr/bin/zip' UNZIP_BIN = '/usr/bin/unzip' class ReceivedApp(object): def __init__(self, path): self.path = path def unpack_to_dir(self, unpack_dir): app_name = os.path.basename(self.path) target_dir = os.path.join(unpack_dir, app_name) shutil.copytree(self.path, target_dir) return App(target_dir) class ReceivedIpaApp(ReceivedApp): def unpack_to_dir(self, target_dir): call([UNZIP_BIN, "-qu", self.path, "-d", target_dir]) return IpaApp(target_dir) class App(object): def __init__(self, path): self.path = path self.entitlements_path = os.path.join(self.path, 'Entitlements.plist') self.app_dir = self.get_app_dir() self.provision_path = os.path.join(self.app_dir, 'embedded.mobileprovision') def get_app_dir(self): return self.path def provision(self, provision_path): print "provision_path: {0}".format(provision_path) shutil.copyfile(provision_path, self.provision_path) def create_entitlements(self): # we decode part of the provision path, then extract the # Entitlements part, then write that to a file in the app. # piping to Plistbuddy doesn't seem to work :( # hence, temporary intermediate file decoded_provision_fh, decoded_provision_path = tempfile.mkstemp() decoded_provision_fh = open(decoded_provision_path, 'w') decode_args = [SECURITY_BIN, 'cms', '-D', '-i', self.provision_path] process = Popen(decode_args, stdout=decoded_provision_fh) # if we don't wait for this to complete, it's likely # the next part will see a zero-length file process.wait() get_entitlements_cmd = [ PLIST_BUDDY_BIN, '-x', '-c', 'print :Entitlements ', decoded_provision_path] entitlements_fh = open(self.entitlements_path, 'w') process2 = Popen(get_entitlements_cmd, stdout=entitlements_fh) process2.wait() entitlements_fh.close() # should destroy the file decoded_provision_fh.close() def codesign(self, certificate, path, extra_args=[]): call([CODESIGN_BIN, '-f', '-s', certificate] + extra_args + [path]) def sign(self, certificate): # first sign all the dylibs frameworks_path = os.path.join(self.app_dir, 'Frameworks') if os.path.exists(frameworks_path): dylibs = glob.glob(os.path.join(frameworks_path, '*.dylib')) for dylib in dylibs: self.codesign(certificate, dylib) # then sign the app self.codesign(certificate, self.app_dir, ['--entitlements', self.entitlements_path]) def package(self, output_path): if not output_path.endswith('.app'): output_path = output_path + '.app' os.rename(self.app_dir, output_path) return output_path class IpaApp(App): def _get_payload_dir(self): return os.path.join(self.path, "Payload") def get_app_dir(self): glob_path = os.path.join(self._get_payload_dir(), '*.app') apps = glob.glob(glob_path) count = len(apps) if count != 1: err = "Expected 1 app in {0}, found {1}".format(glob_path, count) raise Exception(err) return apps[0] def package(self, output_path): if not output_path.endswith('.ipa'): output_path = output_path + '.ipa' temp = "out.ipa" # need to chdir and use relative paths, because zip is stupid old_cwd = os.getcwd() os.chdir(self.path) relative_payload_path = os.path.relpath(self._get_payload_dir(), self.path) call([ZIP_BIN, "-qr", temp, relative_payload_path]) os.rename(temp, output_path) os.chdir(old_cwd) return output_path def absolute_path_argument(path): return os.path.abspath(path) def exists_absolute_path_argument(path): if not os.path.exists(path): raise argparse.ArgumentTypeError("%s does not exist!" % path) return absolute_path_argument(path) def app_argument(path): path = exists_absolute_path_argument(path) _, extension = os.path.splitext(path) if extension == '.app': app = ReceivedApp(path) elif extension == '.ipa': app = ReceivedIpaApp(path) else: raise argparse.ArgumentTypeError( "{0} doesn't seem to be an .app or .ipa".format(path)) return app def parse_args(): parser = argparse.ArgumentParser( description='Resign an iOS application with a new identity ' 'and provisioning profile.') parser.add_argument( '-p', '--provisioning-profile', dest='provisioning_profile', required=True, metavar='', type=exists_absolute_path_argument, help='Path to provisioning profile') parser.add_argument( '-c', '--certificate', dest='certificate', required=True, metavar='', help='Identifier for the certificate in your keychain. ' 'See `security find-identity` for a list, or ' '`man codesign` for valid ways to specify it.') parser.add_argument( '-s', '--staging', dest='stage_dir', required=False, metavar='', type=absolute_path_argument, default=os.path.join(os.getcwd(), 'stage'), help='Path to stage directory.') parser.add_argument( '-o', '--output', dest='output_path', required=False, metavar='', type=absolute_path_argument, default=os.path.join(os.getcwd(), 'out'), help='Path to output file or directory') parser.add_argument( 'app', nargs=1, metavar='', type=app_argument, help='Path to application to re-sign, typically a ' 'directory ending in .app or file ending in .ipa.') return parser.parse_args() if __name__ == '__main__': args = parse_args() received_app = args.app[0] if os.path.exists(args.stage_dir): shutil.rmtree(args.stage_dir) os.mkdir(args.stage_dir) app = received_app.unpack_to_dir(args.stage_dir) app.provision(args.provisioning_profile) app.create_entitlements() app.sign(args.certificate) output_path = app.package(args.output_path) if os.path.exists(args.stage_dir): shutil.rmtree(args.stage_dir) print "Re-signed package: {0}".format(output_path) ================================================ FILE: apple/provisions.sh ================================================ #!/bin/bash usage() { echo "./provisions -p [PATH_TO_NEW_PROVISIONING_PROFILE] -c \"CERT NAME: MUST BE IN KEYCHAIN\" ipa_file" exit } while getopts ":p:c:" opt; do case $opt in p ) PRO_PROFILE=$OPTARG ;; c ) CERT_NAME=$OPTARG ;; /? ) usage esac done shift $(($OPTIND - 1)) if [[ -z "$@" ]]; then usage else APP=$@ fi verify_args() { if [[ ! -e $APP ]]; then echo "$APP does not exist" exit 1 elif [[ ! -e $PRO_PROFILE ]]; then echo "$PRO_PROFILE does not exist" exit 1 elif [[ -z $CERT_NAME ]]; then echo "Must specify a certificate to use" exit 1 fi } is_app() { [[ $APP =~ \.app$ ]] } is_ipa() { [[ $APP =~ \.ipa$ ]] } setup_dir() { STAGE_DIR=./stage ENTITLEMENTS_FILE=$STAGE_DIR/Entitlements.plist if [[ -e $STAGE_DIR ]]; then rm -r $STAGE_DIR fi mkdir $STAGE_DIR if is_app; then cp -r $APP $STAGE_DIR APP_NAME=$(basename $APP) PAYLOAD_DIR="" APP_DIR=$STAGE_DIR/$APP_NAME elif is_ipa; then unzip -qu $APP -d $STAGE_DIR PAYLOAD_DIR=$STAGE_DIR/Payload APP_DIR=$PAYLOAD_DIR/*.app else echo "Must provide either an .app or .ipa file" exit 1 fi } copy_profile() { cp "$PRO_PROFILE" $APP_DIR/embedded.mobileprovision } create_entitlements() { /usr/libexec/PlistBuddy -x -c "print :Entitlements " /dev/stdin <<< $(security cms -D -i ${APP_DIR}/embedded.mobileprovision) > $ENTITLEMENTS_FILE } sign_app() { if [ -e $APP_DIR/Frameworks ]; then for dylib in "$APP_DIR/Frameworks/*" do echo "signing $dylib" # entitlements are irrelevant to dylibs /usr/bin/codesign -f -s "$CERT_NAME" $dylib done fi echo "signing $APP_DIR"; /usr/bin/codesign -f -s "$CERT_NAME" --entitlements $ENTITLEMENTS_FILE $APP_DIR 2>/dev/null } package_app() { if is_ipa; then (cd $STAGE_DIR; zip -qr out.ipa Payload) echo "Re-provisioned ipa at $STAGE_DIR/out.ipa" else echo "Re-provisioned app at $APP_DIR" fi } verify_args setup_dir copy_profile create_entitlements sign_app package_app ================================================ FILE: bin/isign ================================================ #!/usr/bin/env python # -*- coding: utf-8 -*- import argparse from isign import isign from os.path import abspath, expanduser import logging FORMATTER = logging.Formatter('%(message)s') def log_to_stderr(level=logging.INFO): root = logging.getLogger() root.setLevel(level) handler = logging.StreamHandler() handler.setFormatter(FORMATTER) root.addHandler(handler) def absolute_path_argument(path): return abspath(expanduser(path)) def parse_args(): # note that for arguments which eventually get fed into # isign.resign, we deliberately don't set defaults. The kwarg # defaults in isign.resign will be used parser = argparse.ArgumentParser( description='Resign an iOS application with a new identity ' 'and provisioning profile. See documentation for ' 'how to obtain properly formatted credentials.') parser.add_argument( '-p', '--provisioning-profile', dest='provisioning_profile', required=False, metavar='', type=absolute_path_argument, help='Path to provisioning profile' ) parser.add_argument( '-a', '--apple-cert', dest='apple_cert', required=False, metavar='', type=absolute_path_argument, help='Path to Apple certificate in PEM form. You only need to specify ' 'this if the Apple certificate in the isign package is out of ' 'date.' ) parser.add_argument( '-k', '--key', dest='key', required=False, metavar='', type=absolute_path_argument, help='Path to your organization\'s key in PEM format.' ) parser.add_argument( '-c', '--certificate', dest='certificate', required=False, metavar='', type=absolute_path_argument, help='Path to your organization\'s certificate in PEM format' ) parser.add_argument( '-n', '--credentials', dest='credentials_dir', required=False, metavar='', help='Equivalent to ' '-k /key.pem ' '-c /certificate.pem ' '-p /isign.mobileprovision' ) parser.add_argument( '-o', '--output', dest='output_path', required=False, metavar='', type=absolute_path_argument, help='Path to output file or directory' ) parser.add_argument( 'app_paths', nargs=1, metavar='', type=absolute_path_argument, help='Path to application to re-sign, typically a ' 'directory ending in .app or file ending in .ipa.' ) parser.add_argument( '-v', '--verbose', dest='verbose', action='store_true', default=False, required=False, help='Set logging level to debug.' ) parser.add_argument( '-i', '--info', dest='info_props', required=False, metavar='', help='List of comma-delimited key=value pairs of Info.plist properties to override' ) parser.add_argument( '-d', '--display', dest='display_only', action='store_true', default=False, required=False, help='Display information about the app without resigning' ) parser.add_argument( '-e', '--entitlements', dest='alternate_entitlements_path', required=False, metavar='', type=absolute_path_argument, help='Sign with these entitlements, rather than ones extracted from provisioning profile' ) return parser.parse_args() def filter_args(args, interested): """ Filter all args to args that we are interested in """ # We want the unused command line args to be # missing in kwargs, so the defaults are used kwargs = {} for k, v in vars(args).iteritems(): if k in interested and v is not None: kwargs[k] = v return kwargs if __name__ == '__main__': args = parse_args() if args.verbose: level = logging.DEBUG else: level = logging.INFO log_to_stderr(level) if args.display_only: # Only show information import json bundle_info = isign.view(args.app_paths[0]) print json.dumps(bundle_info, indent=4, separators=(',', ': ')) else: # Handle the various kinds of resign operations kwargs = {} # There's only one output path, so it doesn't make sense # to have multiple input paths app_path = args.app_paths[0] # Convert the Info.plist property pairs to a dict format if args.info_props: info_props = {} for arg in args.info_props.split(','): i = arg.find('=') if i < 0: raise Exception('Invalid Info.plist argument: ' + arg) info_props[arg[0:i]] = arg[i + 1:] if info_props: kwargs['info_props'] = info_props if args.credentials_dir: # Handle a resign with credentials directory. # First check they haven't over-specified credential paths incompatible_args = ['certificate', 'key', 'provisioning_profile'] args_dict = vars(args) for k, v in vars(args).iteritems(): if k in incompatible_args and v is not None: raise Exception("Incompatible arguments. Do not use any of " + ", ".join(['--' + s for s in incompatible_args]) + " " "with the --credentials argument.") # looks good, now massage args into method arguments resign_args = ['apple_cert', 'output_path'] kwargs.update(filter_args(args, resign_args)) isign.resign_with_creds_dir(app_path, args.credentials_dir, **kwargs) else: # Handle standard resign. User may have specified all, some # or none of the credential files, in which case we rely on # isign.resign() to supply defaults. # Massage args into method arguments resign_args = ['certificate', 'key', 'apple_cert', 'provisioning_profile', 'output_path', 'alternate_entitlements_path'] kwargs.update(filter_args(args, resign_args)) isign.resign(app_path, **kwargs) ================================================ FILE: bin/isign_export_creds.sh ================================================ #!/usr/bin/env bash # Export a certificate and key from a .p12 file into PEM form, and place # them where isign expects them to be. # # The .p12 file is typically exported from Apple's Keychain Access. if [[ $# -eq 0 ]]; then echo "Usage: $0 exported.p12 [target_directory]" exit 1; fi p12_path=$1 if [[ ! -e $p12_path ]]; then echo "Can't find $p12_path!"; exit 1; fi target_dir=${2-$HOME/.isign} target_cert_path=$target_dir/certificate.pem target_key_path=$target_dir/key.pem chmod 600 $p12_path mkdir -p $target_dir openssl pkcs12 -in $p12_path -out $target_cert_path -clcerts -nokeys openssl pkcs12 -in $p12_path -out $target_key_path -nocerts -nodes chmod 600 $target_key_path read -p "Done exporting $p12_path. Remove it? [Y/n]:" -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]]; then rm $p12_path fi echo "Exported credentials from $p12_path to $target_dir" read -p "Find matching provisioning profile? [Y/n]:" -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]]; then ./guess_mobileprovision.sh $target_cert_path fi ================================================ FILE: bin/isign_guess_mobileprovision.sh ================================================ #!/usr/bin/env bash # given the filename certificate in PEM form, find potentially matching .mobileprovision files # in the usual directory on Mac OS X PROVISIONING_PROFILE_DIR="$HOME/Library/MobileDevice/Provisioning Profiles" cert_path=${1-$HOME/.isign/certificate.pem} echo "Looking for provisioning profiles signed with $cert_path..." get_cert() { cert_path=$1 in_certificate=1 certificate='' while read line; do if [[ $line =~ 'BEGIN CERTIFICATE' ]]; then in_certificate=0 continue fi if [[ $line =~ 'END CERTIFICATE' ]]; then in_certificate=1 continue fi if [[ $in_certificate -eq 0 ]]; then # trim leading/trailing whitespace line="$(echo -e $line | sed -e 's/^[[:space:]]*//' | sed -e 's/[[:space:]]*$//')" certificate="${certificate}${line}" fi done < $cert_path echo $certificate } target_cert=$(get_cert $cert_path) find "$PROVISIONING_PROFILE_DIR/mobdev1.mobileprovision" -type f -print0 | while IFS= read -r -d '' mobileprovision; do # extract the cert from the mobileprovision with `security` mobileprovision_data=$(security cms -D -i "$mobileprovision") # PlistBuddy doesn't give array lengths, so we don't know how many certs this mobileprovision might have. Try a few for i in `seq 0 10`; do # because Plistbuddy is dumb, we have to use a convoluted shell syntax to read from stdin # finally base64 it so we can do an easy string comparison # TODO: can't quite figure out why, but I need to pipe it to base64, can't do that encoding later. # But this means I can't figure out if the PlistBuddy succeeded or failed, by checking $? # Have to swallow the error, iterate more times than necessary :( cert=$(/usr/libexec/PlistBuddy -c "Print :DeveloperCertificates:$i" /dev/stdin <<< $(echo $mobileprovision_data) 2>/dev/null | base64) # Examine a long prefix (there are some issues with padding & zeroes at the end). # if first thousand match it is almost certainly a match if [[ ${cert:0:1000} = ${target_cert:0:1000} ]]; then echo "\"$mobileprovision\" was signed with this certificate." fi done done ================================================ FILE: bin/make_seal ================================================ #!/usr/bin/env python # -*- coding: utf-8 -*- from optparse import OptionParser import isign.code_resources import logging FORMATTER = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') def log_to_stderr(level=logging.INFO): root = logging.getLogger() root.setLevel(level) handler = logging.StreamHandler() handler.setFormatter(FORMATTER) root.addHandler(handler) if __name__ == '__main__': log_to_stderr() parser = OptionParser() options, args = parser.parse_args() source_app, target_dir = args isign.code_resources.make_seal(source_app, target_dir) ================================================ FILE: bin/multisign ================================================ #!/usr/bin/env python # -*- coding: utf-8 -*- # From a single IPA, generate multiple re-signed IPAs simultaneously. # Why? Because you might want to distribute an app to a lot of organizations at once, # or perhaps you need to sign for an enterprise and a local debug deployment all at # the same time, and you want it to be fast. # Depends on the existence of external `zip` and `unzip` programs. import argparse from os.path import abspath, basename, dirname, expanduser, join from isign.multisign import multisign import logging FORMATTER = logging.Formatter('%(message)s') log = logging.getLogger(__name__) def log_to_stderr(level=logging.INFO): root = logging.getLogger() root.setLevel(level) handler = logging.StreamHandler() handler.setFormatter(FORMATTER) root.addHandler(handler) def absolute_path_argument(path): return abspath(expanduser(path)) def parse_args(): parser = argparse.ArgumentParser( description='From a single IPA, generate multiple re-signed IPAs simultaneously') parser.add_argument( '-a', '--app', dest='app', required=True, metavar='', type=absolute_path_argument, help='Path to input app' ) parser.add_argument( 'credential_dirs', nargs='+', metavar='', type=absolute_path_argument, help='Paths to directories containing credentials with standardized names' ) parser.add_argument( '-i', '--info', dest='info_props', required=False, metavar='', help='List of comma-delimited key=value pairs of Info.plist properties to override' ) parser.add_argument( '-v', '--verbose', dest='verbose', action='store_true', default=False, required=False, help='Set logging level to debug.' ) return parser.parse_args() def get_output_path(original_path, credential_dir): """ Create some filename based on the credential directory and original path """ original_name = basename(original_path) original_dir = dirname(original_path) resigned_name = basename(credential_dir) + '_' + original_name return join(original_dir, resigned_name) if __name__ == '__main__': args = parse_args() if args.verbose: level = logging.DEBUG else: level = logging.INFO log_to_stderr(level) # Convert the Info.plist property pairs to a dict format info_props = None if args.info_props: info_props = {} for arg in args.info_props.split(','): i = arg.find('=') if i < 0: raise Exception('Invalid Info.plist argument: ' + arg) info_props[arg[0:i]] = arg[i + 1:] log.debug('got credential paths: {}'.format(', '.join(args.credential_dirs))) log.debug('got incoming app: {}'.format(args.app)) # ensure basenames are unique unique_basenames = set([basename(p) for p in args.credential_dirs]) if len(args.credential_dirs) != len(unique_basenames): raise Exception('Credential directories do not have unique basenames. ' 'Cannot name output archives automatically. Sorry!') credential_dirs_to_output_paths = {} for d in args.credential_dirs: credential_dirs_to_output_paths[d] = get_output_path(args.app, d) results = multisign(args.app, credential_dirs_to_output_paths, info_props) for credentials_dir, resigned_app_path in results: log.info("resigned with %s to %s", credentials_dir, resigned_app_path) ================================================ FILE: bin/pprint_codesig ================================================ #!/usr/bin/env python # -*- coding: utf-8 -*- import argparse import isign.archive from isign.exceptions import NotSignable from isign.signable import Executable import logging from os.path import abspath, expanduser, isdir import shutil FORMATTER = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') log = logging.getLogger(__name__) def log_to_stderr(level=logging.INFO): root = logging.getLogger() root.setLevel(level) handler = logging.StreamHandler() handler.setFormatter(FORMATTER) root.addHandler(handler) def absolute_path_argument(path): return abspath(expanduser(path)) def print_codesig(path): temp_dir = None try: archive = isign.archive.archive_factory(path) (temp_dir, bundle) = appArchive.unarchive_to_temp() _print_codesig(bundle) except NotSignable as e: msg = "Not signable: <{0}> {1}\n".format(path, e) log.error(msg) raise finally: if temp_dir is not None and isdir(temp_dir): shutil.rmtree(temp_dir) def _print_codesig(bundle): executable = Executable(bundle.get_executable_path()) for arch in executable.arches: print arch['cmds']['LC_CODE_SIGNATURE'] def parse_args(): parser = argparse.ArgumentParser( description='Print the code signature structure of an executable or dylib') parser.add_argument( '-v', '--verbose', dest='verbose', action='store_true', default=False, required=False, help='Set logging level to debug.' ) parser.add_argument( 'app_paths', nargs=1, metavar='', type=absolute_path_argument, help='Path(s) to application, typically a ' 'directory ending in .app or file ending in .ipa.' ) return parser.parse_args() if __name__ == '__main__': args = parse_args() if args.verbose: level = logging.DEBUG else: level = logging.INFO log_to_stderr(level) print_codesig(args.app_paths[0]) ================================================ FILE: dev/requirements.txt ================================================ nose pytest twine ================================================ FILE: dev/setup.sh ================================================ #!/bin/bash set -e # determine local paths pushd $(dirname "$0") >/dev/null DEV_DIR=$PWD cd .. SRC_DIR=$PWD popd >/dev/null pip install -U -r ${DEV_DIR}/requirements.txt ../INSTALL.sh develop ================================================ FILE: docs/applecerts.rst ================================================ Apple certificates ================== You probably don't need to change this file, not for a long time. The ``applecerts.pem`` file can be constructed by these steps. In theory you can export them from Keychain Access, too, but here's a procedure that doesn't involve an Apple machine. This worked for us in June 2016: .. code:: bash $ curl 'https://www.apple.com/appleca/AppleIncRootCertificate.cer' > AppleIncRootCertificate.cer $ curl 'https://developer.apple.com/certificationauthority/AppleWWDRCA.cer' > AppleWWDRCA.cer $ openssl x509 -inform der -in AppleIncRootCertificate.cer -outform pem -out AppleIncRootCertificate.pem $ openssl x509 -inform der -in AppleWWDRCA.cer -outform pem -out AppleWWDRCA.pem $ cat AppleWWDRCA.pem AppleIncRootCertificate.pem > applecerts.pem Here's a conceptual explanation of what we're doing: Download the following certs from `Apple's Certificate Authority Page `__ - Apple Inc. Root Certificate - Worldwide Developer Relations Certificate Then convert these to `PEM `__ format. Then, concatenate them together. This file can now serve as the 'Apple certificate' for code signing. ================================================ FILE: docs/codedirectory.rst ================================================ CodeDirectory slots =================== A signature is mostly composed of hashes of blocks of the file's contents. However, at some point, Apple added special hashes so the state of other resources in the app could be captured in the signature. For instance, the Info.plist gets its own hash, and ultimately the hashes of all the other files are also captured in the ResourceDirectory hash. Together, all these special hashes are called the CodeDirectory. Perhaps to indicate that these are special hashes, they were given negative offsets in the list of hashes. For instance, if you do ``codesign -d -r- --verbose=20 some.app`` :: Executable=... Identifier=com.somecompany.someapp Format=bundle with Mach-O universal (armv7 arm64) CodeDirectory v=20200 size=874 flags=0x0(none) hashes=35+5 location=embedded Hash type=sha1 size=20 -5=0ea763a5bc4d19b0e03315a956deecd97693a661 -4=0000000000000000000000000000000000000000 -3=b353e6ce8464fd8ae32cfcf09e7c9015b7378054 -2=32a5edb9b03a0bea2d7bc30cfdddadab7dab841c -1=46ebe92997b23b2e2187a21714c8cc32c347bf35 0=70e024fdab3426c375cf283d384de58ec6fff438 1=1ceaf73df40e531df3bfb26b4fb7cd95fb7bff1d 2=1ceaf73df40e531df3bfb26b4fb7cd95fb7bff1d ... The CodeDirectory hashes have stable negative indices - for instance, -1 is always the hash of the Info.plist file. The indices for the CodeDirectory hashes are sometimes called slots. When building the CodeDirectory, We need to observe these constraints: - Executables should have all 5 slots in their codedirectory - Dylibs only need 2 slots, but sometimes have been compiled with 5 - Dylibs should never include the ResourceDir slot, even if they have 5 slots - We should delay calculating hashes until we know we are going to use them - Nobody uses the Application-specific slot anyway - At least so far, we don't need to change the Info.plist slot when re-signing ================================================ FILE: docs/credentials.rst ================================================ Apple developer credentials =========================== Mac OS X and iOS aren't like most other operating systems -- security is baked into every single program. The machine is able to check, before it even executes the program, who wrote it, whether Apple approved it, and whether it has the authority to do some things that it's asking to do. On Mac OS X, it's easy to get around those restrictions, but iOS is locked down very tight. The iOS device is able tell who wrote a program, and whether Apple approved it, without even phoning home to Apple. It does this by looking for evidence that is *was* approved at some earlier date, using cryptographic signatures embedded right into the application. Until now, few people outside Apple had a full understanding of how that worked, or how to add that magic "code signature" and other necessary proofs to the app. ``isign`` is able to create those -- with the right credentials. Definitions ----------- The **key** is the developer's private key. If you're familiar with Secure Shell keys, this is similar -- it's the private half of a public-private key pair. The private key is used to sign the application. This proves that the developer approved the entire contents of the application. Note that this key is not revealed to Apple, or anyone else. If you develop iOS apps, you, or someone in your organization, generated such a key. Hopefully they kept it secret. However, you need it to sign the app -- you probably have it in Keychain Access or something like that. The **certificate** is how Apple indicates that the developer is approved to write apps for iOS. At some point, they signed the public half of the developer's keys. Apple will also encode when your developer account is going to expire. The **organizational unit** is an identifier that Apple assigns to organizations that create apps on Apple devices. It's an eight character code of letters and numbers. You may need to know your organizational unit, so you can sign apps from your organization. ``isign`` can extract this from other files, like your certificate, so you probably don't need to worry about this. The **provisioning profile** is where it all comes together. This file is included in the app. The provisioning profile includes the contents of the certificate -- possibly many certificates. It also includes the UDIDs of the devices that you have registered with Apple. So, you can often use the same provisioning profile for all your developers and all your iOS devices. So, when a phone encounters an app, it will start by reading the provisioning profile, to learn things like: - Who wrote this app? - What organization do they belong to? - Are they still an Apple-approved developer? - And what's their public key? - Is this app approved to run on this particular device? From there it can go on to cryptographically verify each and every part of the app. This ensures that the app has not been tampered with, or that an unapproved developer hasn't snuck an app onto your phone (even if that developer is you.) How to create Apple developer credentials ----------------------------------------- For most developers, XCode takes care of most of these credentials. But with ``isign`` you have to do it more manually. Getting an account ~~~~~~~~~~~~~~~~~~ First, find an administrator for your developer organization at Apple. (If you're a solo developer, that's you.) Get them to invite you to your Apple Organizational Unit. You'll get email from Apple, which will prompt you to set up your account. Set up passwords and so on as usual. Troubleshooting: you may have to click on the mailed invite link once to set up your account, and then return to your mail to click on that invite link again to actually activate your account. Also, in general, things on the Apple site work better with Safari, so if something doesn't work, try that browser. Setting up credentials ~~~~~~~~~~~~~~~~~~~~~~ The following procedure works as of June 2015, for adding a new account from scratch. You will need a Mac OS X computer. (In theory you could do this entirely from Linux, using the Apple website, but you're on your own there.) Log into your Apple account. Go to **Certificates, Identifiers, & Profiles**. Go to **Certificates > Development** in the left-hand column. Press the plus icon to add a new certificate. It will ask what type of certificate you need. You probably want *iOS App Development*. It will then instruct you to use Keychain Access to generate a Certificate Signing Request. In effect you are going to create a private/public key pair, and then make a little file that says "Hey Apple, please sign the public half and make a certificate for it, and associate that with my Apple account!" Follow the instructions and save that CSR to disk. Press **Continue**. Then, the Apple website will ask you to upload that CSR. Do so, and it will create a certificate for your account in your organization. This certificate might need to be approved by an admin before you can download it. Once it's approved, download it! It will probably be named something generic like ``ios_development.cer``, so rename it to something more meaningful and put it somewhere safe. Import that ``.cer`` into Keychain. Keychain will detect that it has an associated private key, and in views where you see keys, the certificate will be "inside" the key, and vice versa. Provisioning profile ~~~~~~~~~~~~~~~~~~~~ Now we just need to tell Apple that your user is allowed to deploy on those devices. First, go to **Devices**, and add the UDIDs of all the devices you care about. If you installed `libimobiledevice`, an easy way to get the UDID is with ``idevice_id -l``. In the Developer portal, go to **Provisioning Profiles**, and create a new development profile. Select those devices and add them. In **Select App ID**, you probably want to just create one with a wildcard (Something using your Apple Organizational Unit plus domain plus dot-star, maybe, like ``A1B2C3D4.tld.domain.*`` ) Next, in **'Select certificates'**, select the certificates you want, which probably includes the one we just created above. Finally, download this provisioning profile, and follow the instructions in the main README. ================================================ FILE: docs/speed.rst ================================================ So you want to make it go faster ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The *vast* majority of time in isign is not spent signing. It's in zipping and re-zipping. Most apps are packaged as IPAs. These are just glorified zip files with a particular directory structure. For most situations, we can't avoid the cost of unzipping and re-zipping. So if we're trying to make it faster, we've already lost, since we'll never speed it up even by a factor of 2. In theory you could: - Not use IPAs. isign can also re-sign unzipped ".app" directories. This is way faster, but then you have to use isign where you created the app, and you have to install the resigned app onto a device from there as well. - Use IPAs but re-compress them poorly. Basically, change the setting from zip quality of 6 (the default) to something else. Minor speedups can be accomplished that way, at the cost of increasing install time or time to transfer to send it the network. In my tests it wasn't an improvement when you considered typical things you would do with the app next. If neither of those approaches is acceptable to you, you can stop reading here. Because everything else that follows will speed it up, but not by much. Okay, so what *else* is taking time? 1) Copying the files. Maybe with some exotic copy-on-write filesystem we could avoid that? 2) Hashing the entire contents of the application. For data that we've never seen before, we can't do better than O(n). So we lose *again*. But we could still do better in the general case: - There are many files within the application. If we don't touch a file (it's not an executable or dylib), and we got a hash in the app's existing CodeResources seal, we could trust it, and reuse it in our CodeResources seal. I tried implementing this and it was super annoying to code, and didn't have a big impact on my test files, so I gave up. The problem is that you have to write wrappers around every method that writes to a file, and keep a tally of what files you touched. If you don't mind a less accurate method (scan directories for files changed since the start of the resigning process) that might work better. - We could recognize common libraries such as the Swift framework, and keep re-signed versions of those in some persistent storage. - Use separate processes to hash files, to exploit multiple cores. But wait! ~~~~~~~~~ Incidentally, if what you're looking for is to resign one app with multiple credentials, look into the `multisign` scripts. There can save a significant amount of time by unzipping the original app only once, and using a process pool to exploit multiple cores. ================================================ FILE: isign/__init__.py ================================================ # -*- coding: utf-8 -*- import os.path import json package_dir = os.path.dirname(os.path.realpath(__file__)) with open(os.path.join(package_dir, "version.json"), 'r') as f: version = json.load(f) __version__ = version['version'] __commit__ = version['commit'] __build__ = version['build'] ================================================ FILE: isign/apple_credentials/README.rst ================================================ These Apple credentials are public knowledge (see the docs) so they are okay to distribute. Don't put anything else here. ================================================ FILE: isign/archive.py ================================================ # -*- coding: utf-8 -*- """ Represents an app archive. This is an app at rest, whether it's a naked app bundle in a directory, or a zipped app bundle, or an IPA. We have a common interface to extract these apps to a temp file, then resign them, and create an archive of the same type """ import abc import biplist from bundle import App, Bundle, is_info_plist_native from exceptions import MissingHelpers, NotSignable, NotMatched from distutils import spawn import logging import os from os.path import abspath, dirname, exists, isdir, isfile, join, normpath import tempfile import re from subprocess import call from signer import Signer import shutil import zipfile import utils REMOVE_WATCHKIT = True helper_paths = {} log = logging.getLogger(__name__) def get_helper(helper_name): """ find paths to executables. Cached in helper_paths """ if helper_name not in helper_paths or helper_paths[helper_name] is None: # note, find_executable returns None is not found # in other words, we keep retrying until found helper_paths[helper_name] = spawn.find_executable(helper_name) log.debug("got executable {} for {}".format(helper_paths[helper_name], helper_name)) return helper_paths[helper_name] def make_temp_dir(): return tempfile.mkdtemp(prefix="isign-") def get_watchkit_paths(root_bundle_path): """ collect sub-bundles of this bundle that have watchkit """ # typical structure: # # app_bundle # ... # some_directory # watchkit_extension <-- this is the watchkit bundle # Info.plist # watchkit_bundle <-- this is the part that runs on the Watch # Info.plist <-- WKWatchKitApp=True # watchkit_paths = [] for path, _, _ in os.walk(root_bundle_path): if path == root_bundle_path: continue try: bundle = Bundle(path) except NotMatched: # this directory is not a bundle continue if bundle.info.get('WKWatchKitApp') is True: # get the *containing* bundle watchkit_paths.append(dirname(path)) return watchkit_paths def process_watchkit(root_bundle_path, should_remove=False): """ Unfortunately, we currently can't sign WatchKit. If you don't care about watchkit functionality, it is generally harmless to remove it, so that's the default. Remove when https://github.com/saucelabs/isign/issues/20 is fixed """ watchkit_paths = get_watchkit_paths(root_bundle_path) if len(watchkit_paths) > 0: if should_remove: for path in watchkit_paths: log.warning("Removing WatchKit bundle {}".format(path)) shutil.rmtree(path) else: raise NotSignable("Cannot yet sign WatchKit bundles") class Archive(object): __metaclass__ = abc.ABCMeta # we use abc.abstractmethod throughout because there are certain class # methods we want to ensure are implemented. @abc.abstractmethod def unarchive_to_temp(self): """ Unarchive and copy to a temp directory """ pass @abc.abstractmethod def archive(cls, path, output_path): """ Archive a directory to an output path """ pass @abc.abstractmethod def get_info(cls, path): """ Obtain app metadata from Info.plist without unarchiving """ pass @abc.abstractmethod def precheck(cls, path): """ Check if this is, in fact, an archive of this type """ pass @abc.abstractmethod def find_bundle_dir(cls, path): """ Locate the directory of the main app (aka bundle) """ pass class AppArchive(Archive): """ The simplest form of archive -- a naked App Bundle, with no extra directory structure, compression, etc """ @classmethod def find_bundle_dir(cls, path): """ Included for similarity with the zipped archive classes. In this case, the bundle dir *is* the directory """ return path @classmethod def _get_plist_path(cls, path): return join(cls.find_bundle_dir(path), "Info.plist") @classmethod def get_info(cls, path): return biplist.readPlist(cls._get_plist_path(path)) @classmethod def precheck(cls, path): if not isdir(path): return False if not os.path.exists(cls._get_plist_path(path)): return False plist = cls.get_info(path) is_native = is_info_plist_native(plist) log.debug("is_native: {}".format(is_native)) return is_native @classmethod def archive(cls, path, output_path): if exists(output_path): shutil.rmtree(output_path) shutil.move(path, output_path) log.info("archived %s to %s" % (cls.__name__, output_path)) def __init__(self, path): self.path = path self.relative_bundle_dir = '.' self.bundle_info = self.get_info(self.path) def unarchive_to_temp(self): containing_dir = make_temp_dir() log.debug("unarchiving to temp... %s -> %s", self.path, containing_dir) shutil.rmtree(containing_dir) # quirk of copytree, top dir can't exist already shutil.copytree(self.path, containing_dir) process_watchkit(containing_dir, REMOVE_WATCHKIT) return UncompressedArchive(containing_dir, '.', self.__class__) class AppZipArchive(Archive): """ Just like an app, except it's zipped up, and when repackaged, should be re-zipped. """ app_dir_pattern = r'^([^/]+\.app/).*$' extensions = ['.zip'] helpers = ['zip', 'unzip'] @classmethod def is_helpers_present(cls): """ returns False if any of our helper apps wasn't found in class init """ is_present = True for helper_name in cls.helpers: if get_helper(helper_name) is None: log.error("missing helper for class {}: {}".format(cls.__name__, helper_name)) is_present = False break return is_present @classmethod def is_archive_extension_match(cls, path): """ does this path have the right extension """ log.debug('extension match') for extension in cls.extensions: log.debug('extension match: %s', extension) if path.endswith(extension): return True return False @classmethod def find_bundle_dir(cls, zipfile_obj): relative_bundle_dir = None apps = set() file_list = zipfile_obj.namelist() for file_name in file_list: matched = re.match(cls.app_dir_pattern, file_name) if matched: apps.add(matched.group(1)) if len(apps) == 1: log.debug("found one app") relative_bundle_dir = apps.pop() elif len(apps) > 1: log.warning('more than one app found in archive') else: log.warning('no apps found in archive') return relative_bundle_dir @classmethod def _get_plist_path(cls, relative_bundle_dir): return join(relative_bundle_dir, "Info.plist") @classmethod def precheck(cls, path): """ Checks if an archive looks like this kind of app. Have to examine within the zipfile, b/c we don't want to make temp dirs just yet. This recapitulates a very similar precheck in the Bundle class """ if not isfile(path): return False if not cls.is_helpers_present(): raise MissingHelpers("helpers not present") is_native = False log.debug('precheck') log.debug('path: %s', path) if (cls.is_archive_extension_match(path) and zipfile.is_zipfile(path)): log.debug("this is an archive, and a zipfile") zipfile_obj = zipfile.ZipFile(path) relative_bundle_dir = cls.find_bundle_dir(zipfile_obj) if relative_bundle_dir is not None: plist_path = cls._get_plist_path(relative_bundle_dir) if plist_path not in zipfile_obj.namelist(): return False plist = cls.get_info(relative_bundle_dir, zipfile_obj) is_native = is_info_plist_native(plist) log.debug("is_native: {}".format(is_native)) return is_native @classmethod def get_info(cls, relative_bundle_dir, zipfile_obj): plist_path = cls._get_plist_path(relative_bundle_dir) plist_bytes = zipfile_obj.read(plist_path) return biplist.readPlistFromString(plist_bytes) def __init__(self, path): self.path = path zipfile_obj = zipfile.ZipFile(path) self.relative_bundle_dir = self.find_bundle_dir(zipfile_obj) self.bundle_info = self.get_info(self.relative_bundle_dir, zipfile_obj) def unarchive_to_temp(self): containing_dir = make_temp_dir() call([get_helper('unzip'), "-qu", self.path, "-d", containing_dir]) app_dir = abspath(join(containing_dir, self.relative_bundle_dir)) process_watchkit(app_dir, REMOVE_WATCHKIT) return UncompressedArchive(containing_dir, self.relative_bundle_dir, self.__class__) @classmethod def archive(cls, containing_dir, output_path): """ archive this up into a zipfile. Note this is a classmethod, because the caller will use us on a temp directory somewhere """ # the temp file is necessary because zip always adds ".zip" if it # does not have an extension. But we want to respect the desired # output_path's extension, which could be ".ipa" or who knows. # So we move it to the output_path later. # # We also do a little dance with making another temp directory just # to construct the zip file. This is the best way to ensure the an unused # filename. Also, `zip` won't overwrite existing files, so this is safer. temp_zip_dir = None try: # need to chdir and use relative paths, because zip is stupid temp_zip_dir = tempfile.mkdtemp(prefix="isign-zip-") temp_zip_file = join(temp_zip_dir, 'temp.zip') call([get_helper('zip'), "-qr", temp_zip_file, "."], cwd=containing_dir) shutil.move(temp_zip_file, output_path) log.info("archived %s to %s" % (cls.__name__, output_path)) finally: if temp_zip_dir is not None and isdir(temp_zip_dir): shutil.rmtree(temp_zip_dir) class IpaArchive(AppZipArchive): """ IPA is Apple's standard for distributing apps. Much like an AppZip, but slightly different paths """ extensions = ['.ipa'] app_dir_pattern = r'^(Payload/[^/]+\.app/).*$' class UncompressedArchive(object): """ This just keeps track of some state with an unzipped app archive and how to re-zip it back up once re-signed. The bundle is located somewhere inside the containing directory, but might be a few directories down, like in a ContainingDir/Payload/something.app This class is also useful if you have an app that's already unzipped and you want to sign it. """ def __init__(self, path, relative_bundle_dir, archive_class): """ Path is the "Containing dir", the dir at the root level of the unzipped archive (or the dir itself, in the case of an AppArchive archive) relative bundle dir is the dir containing the bundle, e.g. Payload/Foo.app archive class is the kind of archive this was (Ipa, etc.) """ path = utils.remove_control_char(path) self.path = path self.relative_bundle_dir = relative_bundle_dir self.archive_class = archive_class bundle_path = normpath(join(path, relative_bundle_dir)) self.bundle = App(bundle_path) def archive(self, output_path): """ Re-zip this back up, or simply copy it out, depending on what the original archive class did """ self.archive_class.archive(self.path, output_path) def clone(self, target_path): """ Copy the uncompressed archive somewhere else, return initialized UncompressedArchive """ shutil.copytree(self.path, target_path) return self.__class__(target_path, self.relative_bundle_dir, self.archive_class) def remove(self): # the containing dir might be gone already b/c AppArchive simply moves # it to the desired target when done if exists(self.path) and isdir(self.path): log.debug('removing ua: %s', self.path) shutil.rmtree(self.path) def archive_factory(path): """ Guess what kind of archive we are dealing with, return an archive object. Returns None if path did not match any archive type """ archive = None for cls in [IpaArchive, AppZipArchive, AppArchive]: if cls.precheck(path): archive = cls(path) log.debug("File %s matched as %s", path, cls.__name__) break return archive def view(input_path): if not exists(input_path): raise IOError("{0} not found".format(input_path)) ua = None bundle_info = None try: archive = archive_factory(input_path) if archive is None: raise NotMatched('No matching archive type found') ua = archive.unarchive_to_temp() bundle_info = ua.bundle.info finally: if ua is not None: ua.remove() return bundle_info def resign(input_path, certificate, key, apple_cert, provisioning_profile, output_path, info_props=None, alternate_entitlements_path=None): """ Unified interface to extract any kind of archive from a temporary file, resign it with these credentials, and create a similar archive for that resigned app """ if not exists(input_path): raise IOError("{0} not found".format(input_path)) log.debug('Signing with apple_cert: {}'.format(apple_cert)) log.debug('Signing with key: {}'.format(key)) log.debug('Signing with certificate: {}'.format(certificate)) log.debug('Signing with provisioning_profile: {}'.format(provisioning_profile)) signer = Signer(signer_cert_file=certificate, signer_key_file=key, apple_cert_file=apple_cert) ua = None bundle_info = None try: archive = archive_factory(input_path) if archive is None: raise NotSignable('No matching archive type found') ua = archive.unarchive_to_temp() if info_props: # Override info.plist props of the parent bundle ua.bundle.update_info_props(info_props) ua.bundle.resign(signer, provisioning_profile, alternate_entitlements_path) bundle_info = ua.bundle.info ua.archive(output_path) except NotSignable as e: msg = "Not signable: <{0}>: {1}\n".format(input_path, e) log.info(msg) raise finally: if ua is not None: ua.remove() return bundle_info ================================================ FILE: isign/bundle.py ================================================ # -*- coding: utf-8 -*- """ Represents a bundle. In the words of the Apple docs, it's a convenient way to deliver software. Really it's a particular kind of directory structure, with one main executable, well-known places for various data files and libraries, and tracking hashes of all those files for signing purposes. For isign, we have two main kinds of bundles: the App, and the Framework (a reusable library packaged along with its data files.) An App may contain many Frameworks, but a Framework has to be re-signed independently. See the Apple Developer Documentation "About Bundles" """ import biplist import code_resources from exceptions import NotMatched import copy import glob import logging import os from os.path import basename, exists, join, splitext from signer import openssl_command import signable import shutil import utils log = logging.getLogger(__name__) def is_info_plist_native(plist): """ If an bundle is for native iOS, it has these properties in the Info.plist """ return ( 'CFBundleSupportedPlatforms' in plist and 'iPhoneOS' in plist['CFBundleSupportedPlatforms'] ) class Bundle(object): """ A bundle is a standard directory structure, a signable, installable set of files. Apps are Bundles, but so are some kinds of Frameworks (libraries) """ helpers = [] signable_class = None entitlements_path = None # Not set for every bundle type def __init__(self, path): # Remove Control character path = utils.remove_control_char(path) self.path = path self.info_path = join(self.path, 'Info.plist') if not exists(self.info_path): raise NotMatched("no Info.plist found; probably not a bundle") self.info = biplist.readPlist(self.info_path) self.orig_info = None if not is_info_plist_native(self.info): # while we should probably not allow this *or* add it ourselves, it appears to work without it log.debug(u"Missing/invalid CFBundleSupportedPlatforms value in {}".format(self.info_path)) # will be added later self.seal_path = None def get_entitlements_path(self): return self.entitlements_path def get_executable_path(self): """ Path to the main executable. For an app, this is app itself. For a Framework, this is the main framework """ executable_name = None if 'CFBundleExecutable' in self.info: executable_name = self.info['CFBundleExecutable'] else: executable_name, _ = splitext(basename(self.path)) executable_name = utils.remove_control_char(executable_name) executable = join(self.path, executable_name) if not exists(executable): raise Exception( 'could not find executable for {0}'.format(self.path)) return executable def update_info_props(self, new_props): if self.orig_info is None: self.orig_info = copy.deepcopy(self.info) changed = False if ('CFBundleIdentifier' in new_props and 'CFBundleURLTypes' in self.info and 'CFBundleURLTypes' not in new_props): # The bundle identifier changed. Check CFBundleURLTypes for # CFBundleURLName values matching the old bundle # id if it's not being set explicitly old_bundle_id = self.info['CFBundleIdentifier'] new_bundle_id = new_props['CFBundleIdentifier'] for url_type in self.info['CFBundleURLTypes']: if 'CFBundleURLName' not in url_type: continue if url_type['CFBundleURLName'] == old_bundle_id: url_type['CFBundleURLName'] = new_bundle_id changed = True for key, val in new_props.iteritems(): is_new_key = key not in self.info if is_new_key or self.info[key] != val: if is_new_key: log.warn("Adding new Info.plist key: {}".format(key)) self.info[key] = val changed = True if changed: biplist.writePlist(self.info, self.info_path, binary=True) else: self.orig_info = None def info_props_changed(self): return self.orig_info is not None def info_prop_changed(self, key): if not self.orig_info: # No props have been changed return False if key in self.info and key in self.orig_info and self.info[key] == self.orig_info[key]: return False return True def get_info_prop(self, key): return self.info[key] def sign_dylibs(self, signer, path): """ Sign all the dylibs in this directory """ for dylib_path in glob.glob(join(path, '*.dylib')): dylib = signable.Dylib(self, dylib_path, signer) dylib.sign(self, signer) def sign(self, signer): """ Sign everything in this bundle, recursively with sub-bundles """ # log.debug("SIGNING: %s" % self.path) frameworks_path = join(self.path, 'Frameworks') if exists(frameworks_path): # log.debug("SIGNING FRAMEWORKS: %s" % frameworks_path) # sign all the frameworks for framework_name in os.listdir(frameworks_path): framework_path = join(frameworks_path, framework_name) # log.debug("checking for framework: %s" % framework_path) try: framework = Framework(framework_path) # log.debug("resigning: %s" % framework_path) framework.resign(signer) except NotMatched: # log.debug("not a framework: %s" % framework_path) continue # sign all the dylibs under Frameworks self.sign_dylibs(signer, frameworks_path) # sign any dylibs in the main directory (rare, but it happens) self.sign_dylibs(signer, self.path) plugins_path = join(self.path, 'PlugIns') if exists(plugins_path): # sign the appex executables appex_paths = glob.glob(join(plugins_path, '*.appex')) for appex_path in appex_paths: plist_path = join(appex_path, 'Info.plist') if not exists(plist_path): continue plist = biplist.readPlist(plist_path) appex_exec_path = join(appex_path, plist['CFBundleExecutable']) appex = signable.Appex(self, appex_exec_path, signer) appex.sign(self, signer) # then create the seal # TODO maybe the app should know what its seal path should be... self.seal_path = code_resources.make_seal(self.get_executable_path(), self.path) # then sign the app executable = self.signable_class(self, self.get_executable_path(), signer) executable.sign(self, signer) def resign(self, signer): """ signs bundle, modifies in place """ self.sign(signer) log.debug("Resigned bundle at <%s>", self.path) class Framework(Bundle): """ A bundle that comprises reusable code. Similar to an app in that it has its own resources and metadata. Not like an app because the main executable doesn't have Entitlements, or an Application hash, and it doesn't have its own provisioning profile. """ # the executable in this bundle will be a Framework signable_class = signable.Framework def __init__(self, path): super(Framework, self).__init__(path) class App(Bundle): """ The kind of bundle that is visible as an app to the user. Contains the provisioning profile, entitlements, etc. """ # the executable in this bundle will be an Executable (i.e. the main # executable of an app) signable_class = signable.Executable def __init__(self, path): super(App, self).__init__(path) self.entitlements_path = join(self.path, 'Entitlements.plist') self.provision_path = join(self.path, 'embedded.mobileprovision') def provision(self, provision_path): shutil.copyfile(provision_path, self.provision_path) @staticmethod def extract_entitlements(provision_path): """ Given a path to a provisioning profile, return the entitlements encoded therein """ cmd = [ 'smime', '-inform', 'der', '-verify', # verifies content, prints verification status to STDERR, # outputs content to STDOUT. In our case, will be an XML plist '-noverify', # accept self-signed certs. Not the opposite of -verify! '-in', provision_path ] # this command always prints 'Verification successful' to stderr. (profile_text, err) = openssl_command(cmd, data=None, expect_err=True) if err and err.strip() != 'Verification successful': log.error('Received unexpected error from openssl: {}'.format(err)) plist_dict = biplist.readPlistFromString(profile_text) if 'Entitlements' not in plist_dict: log.debug('failed to get entitlements in provisioning profile') raise Exception('could not find Entitlements in {}'.format(provision_path)) return plist_dict['Entitlements'] def write_entitlements(self, entitlements): """ Write entitlements to self.entitlements_path. This actually doesn't matter to the app, it's just used later on by other parts of the signing process. """ biplist.writePlist(entitlements, self.entitlements_path, binary=False) log.debug("wrote Entitlements to {0}".format(self.entitlements_path)) def resign(self, signer, provisioning_profile, alternate_entitlements_path=None): """ signs app in place """ # TODO all this mucking about with entitlements feels wrong. The entitlements_path is # not actually functional, it's just a way of passing it to later stages of signing. # Maybe we should determine entitlements data in isign/archive.py or even isign/isign.py, # and then embed it into Signer? # In the typical case, we add entitlements from the pprof into the app's signature if alternate_entitlements_path is None: # copy the provisioning profile in self.provision(provisioning_profile) entitlements = self.extract_entitlements(provisioning_profile) else: log.info("signing with alternative entitlements: {}".format(alternate_entitlements_path)) entitlements = biplist.readPlist(alternate_entitlements_path) self.write_entitlements(entitlements) # actually resign this bundle now super(App, self).resign(signer) ================================================ FILE: isign/code_resources.py ================================================ # -*- coding: utf-8 -*- import binascii import copy import hashlib import logging from memoizer import memoize import os import plistlib from plistlib import PlistWriter import re OUTPUT_DIRECTORY = '_CodeSignature' OUTPUT_FILENAME = 'CodeResources' TEMPLATE_FILENAME = 'code_resources_template.xml' # DIGEST_ALGORITHM = "sha1" HASH_BLOCKSIZE = 65536 log = logging.getLogger(__name__) # have to monkey patch Plist, in order to make the values # look the same - no .0 for floats # Apple's plist utils work like this: # 1234.5 ---> 1234.5 # 1234.0 ---> 1234 def writeValue(self, value): if isinstance(value, float): rep = repr(value) if value.is_integer(): rep = repr(int(value)) self.simpleElement("real", rep) else: self.oldWriteValue(value) PlistWriter.oldWriteValue = PlistWriter.writeValue PlistWriter.writeValue = writeValue # Simple reimplementation of ResourceBuilder, in the Apple Open Source # file bundlediskrep.cpp class PathRule(object): OPTIONAL = 0x01 OMITTED = 0x02 NESTED = 0x04 EXCLUSION = 0x10 # unused? TOP = 0x20 # unused? def __init__(self, pattern='', properties=None): # on Mac OS the FS is case-insensitive; simulate that here self.pattern = re.compile(pattern, re.IGNORECASE) self.flags = 0 self.weight = 0 if properties is not None: if type(properties) == 'bool': if properties is False: self.flags |= PathRule.OMITTED # if it was true, this file is required; # do nothing elif isinstance(properties, dict): for key, value in properties.iteritems(): if key == 'optional' and value is True: self.flags |= PathRule.OPTIONAL elif key == 'omit' and value is True: self.flags |= PathRule.OMITTED elif key == 'nested' and value is True: self.flags |= PathRule.NESTED elif key == 'weight': self.weight = float(value) def is_optional(self): return self.flags & PathRule.OPTIONAL != 0 def is_omitted(self): return self.flags & PathRule.OMITTED != 0 def is_nested(self): return self.flags & PathRule.NESTED != 0 def is_exclusion(self): return self.flags & PathRule.EXCLUSION != 0 def is_top(self): return self.flags & PathRule.TOP != 0 def matches(self, path): return re.match(self.pattern, path) def __str__(self): return 'PathRule:' + str(self.flags) + ':' + str(self.weight) class ResourceBuilder(object): NULL_PATH_RULE = PathRule() def __init__(self, app_path, rules_data, respect_omissions=False, include_sha256=False): self.app_path = app_path self.app_dir = os.path.dirname(app_path) self.rules = [] self.respect_omissions = respect_omissions self.include_sha256 = include_sha256 for pattern, properties in rules_data.iteritems(): self.rules.append(PathRule(pattern, properties)) def find_rule(self, path): best_rule = ResourceBuilder.NULL_PATH_RULE for rule in self.rules: # log.debug('trying rule ' + str(rule) + ' against ' + path) if rule.matches(path): if rule.flags and rule.is_exclusion(): best_rule = rule break elif rule.weight > best_rule.weight: best_rule = rule return best_rule def get_rule_and_paths(self, root, path): path = os.path.join(root, path) relative_path = os.path.relpath(path, self.app_dir) rule = self.find_rule(relative_path) return (rule, path, relative_path) def scan(self): """ Walk entire directory, compile mapping path relative to source_dir -> digest and other data """ file_entries = {} # rule_debug_fmt = "rule: {0}, path: {1}, relative_path: {2}" for root, dirs, filenames in os.walk(self.app_dir): # log.debug("root: {0}".format(root)) for filename in filenames: rule, path, relative_path = self.get_rule_and_paths(root, filename) # log.debug(rule_debug_fmt.format(rule, path, relative_path)) # specifically ignore the CodeResources symlink in base directory if it exists (iOS 11+ fix) if relative_path == "CodeResources" and os.path.islink(path): continue if rule.is_exclusion(): continue if rule.is_omitted() and self.respect_omissions is True: continue if self.app_path == path: continue # in the case of symlinks, we don't calculate the hash but rather add a key for it being a symlink if os.path.islink(path): # omit symlinks from files, leave in files2 if not self.respect_omissions: continue val = {'symlink': os.readlink(path)} else: # the Data element in plists is base64-encoded val = {'hash': plistlib.Data(get_hash_binary(path))} if self.include_sha256: val['hash2'] = plistlib.Data(get_hash_binary(path, 'sha256')) if rule.is_optional(): val['optional'] = True if len(val) == 1 and 'hash' in val: file_entries[relative_path] = val['hash'] else: file_entries[relative_path] = val for dirname in dirs: rule, path, relative_path = self.get_rule_and_paths(root, dirname) if rule.is_nested() and '.' not in path: dirs.remove(dirname) continue if relative_path == OUTPUT_DIRECTORY: dirs.remove(dirname) return file_entries def get_template(): """ Obtain the 'template' plist which also contains things like default rules about which files should count """ current_dir = os.path.dirname(os.path.abspath(__file__)) template_path = os.path.join(current_dir, TEMPLATE_FILENAME) fh = open(template_path, 'r') return plistlib.readPlist(fh) @memoize def get_hash_hex(path, hash_type='sha1'): """ Get the hash of a file at path, encoded as hexadecimal """ if hash_type == 'sha256': hasher = hashlib.sha256() elif hash_type == 'sha1': hasher = hashlib.sha1() else: raise ValueError("Incorrect hash type provided: {}".format(hash_type)) with open(path, 'rb') as afile: buf = afile.read(HASH_BLOCKSIZE) while len(buf) > 0: hasher.update(buf) buf = afile.read(HASH_BLOCKSIZE) return hasher.hexdigest() @memoize def get_hash_binary(path, hash_type='sha1'): """ Get the hash of a file at path, encoded as binary """ return binascii.a2b_hex(get_hash_hex(path, hash_type)) def write_plist(target_dir, plist): """ Write the CodeResources file """ output_dir = os.path.join(target_dir, OUTPUT_DIRECTORY) if not os.path.exists(output_dir): os.makedirs(output_dir) output_path = os.path.join(output_dir, OUTPUT_FILENAME) fh = open(output_path, 'w') plistlib.writePlist(plist, fh) return output_path def make_seal(source_app_path, target_dir=None): """ Given a source app, create a CodeResources file for the surrounding directory, and write it into the appropriate path in a target directory """ if target_dir is None: target_dir = os.path.dirname(source_app_path) template = get_template() # n.b. code_resources_template not only contains a template of # what the file should look like; it contains default rules # deciding which files should be part of the seal rules = template['rules'] plist = copy.deepcopy(template) resource_builder = ResourceBuilder(source_app_path, rules, respect_omissions=False) plist['files'] = resource_builder.scan() rules2 = template['rules2'] resource_builder2 = ResourceBuilder(source_app_path, rules2, respect_omissions=True, include_sha256=True) plist['files2'] = resource_builder2.scan() return write_plist(target_dir, plist) ================================================ FILE: isign/code_resources_template.xml ================================================ files rules ^.* ^.*\.lproj/ optional weight 1000 ^.*\.lproj/locversion.plist$ omit weight 1100 ^Base\.lproj/ weight 1010 ^version.plist$ rules2 .*\.dSYM($|/) weight 11 ^(.*/)?\.DS_Store$ omit weight 2000 ^.* ^.*\.lproj/ optional weight 1000 ^.*\.lproj/locversion.plist$ omit weight 1100 ^Base\.lproj/ weight 1010 ^Info\.plist$ omit weight 20 ^PkgInfo$ omit weight 20 ^embedded\.provisionprofile$ weight 20 ^version\.plist$ weight 20 ================================================ FILE: isign/codesig.py ================================================ # -*- coding: utf-8 -*- from abc import ABCMeta import construct import hashlib import logging import macho_cs import utils log = logging.getLogger(__name__) # See the documentation for an explanation of how # CodeDirectory slots work. class CodeDirectorySlot(object): __metaclass__ = ABCMeta offset = None def __init__(self, codesig): self.codesig = codesig 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() else: raise ValueError('Incorrect hash algorith {}'.format(hash_algorithm)) class DerEntitlementsSlot(CodeDirectorySlot): offset = -7 def get_contents(self): blobs = self.codesig.get_blobs('CSMAGIC_DER_ENTITLEMENT', min_expected=1, max_expected=1) return self.codesig.get_blob_data(blobs[0]) class EntitlementsSlot(CodeDirectorySlot): offset = -5 def get_contents(self): blobs = self.codesig.get_blobs('CSMAGIC_ENTITLEMENT', min_expected=1, max_expected=1) return self.codesig.get_blob_data(blobs[0]) class ApplicationSlot(CodeDirectorySlot): offset = -4 def get_hash(self, hash_algorithm): if hash_algorithm == "sha1": hash_length = 20 elif hash_algorithm == "sha256": hash_length = 32 else: raise ValueError('Incorrect hash algorith {}'.format(hash_algorithm)) return '\x00' * hash_length class ResourceDirSlot(CodeDirectorySlot): offset = -3 def __init__(self, seal_path): self.seal_path = seal_path def get_contents(self): return open(self.seal_path, "rb").read() class RequirementsSlot(CodeDirectorySlot): offset = -2 def get_contents(self): blobs = self.codesig.get_blobs('CSMAGIC_REQUIREMENTS', min_expected=1, max_expected=1) return self.codesig.get_blob_data(blobs[0]) class InfoSlot(CodeDirectorySlot): offset = -1 def __init__(self, info_path): self.info_path = info_path def get_contents(self): return open(self.info_path, "rb").read() # Represents a code signature object, aka the LC_CODE_SIGNATURE, # within the Signable class Codesig(object): """ wrapper around construct for code signature """ def __init__(self, signable, data): self.signable = signable self.construct = macho_cs.Blob.parse(data) self.is_sha256 = len(self.construct.data.BlobIndex) >= 6 def is_sha256_signature(self): return self.is_sha256 def build_data(self): return macho_cs.Blob.build(self.construct) 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: 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)) return blobs def get_blob_data(self, blob): """ convenience method, if we just want the data """ return macho_cs.Blob_.build(blob) def set_entitlements(self, entitlements_path): # log.debug("entitlements:") try: 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") 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 def set_requirements(self, signer): # log.debug("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()) signer_cn = signer.get_common_name() # this is for convenience, a reference to the first blob # structure within requirements, which contains the data # we are going to change req_blob_0 = requirements.data.BlobIndex[0].blob req_blob_0_original_length = req_blob_0.length if self.signable.get_changed_bundle_id(): # Set the bundle id if it changed try: bundle_struct = req_blob_0.data.expr.data[0].data bundle_struct.data = self.signable.get_changed_bundle_id() bundle_struct.length = len(bundle_struct.data) except Exception: log.debug("could not set bundle id") try: cn = req_blob_0.data.expr.data[1].data[1].data[0].data[2].Data except Exception: log.debug("no signer CN rule found in requirements") log.debug(requirements) else: # if we could find a signer CN rule, make requirements. # first, replace old signer CN with our own cn.data = signer_cn cn.length = len(cn.data) # req_blob_0 contains that CN, so rebuild it, and get what # the length is now req_blob_0.bytes = macho_cs.Requirement.build(req_blob_0.data) req_blob_0.length = len(req_blob_0.bytes) + 8 # fix offsets of later blobs in requirements offset_delta = req_blob_0.length - req_blob_0_original_length for bi in requirements.data.BlobIndex[1:]: bi.offset += offset_delta # rebuild requirements, and set length for whole thing requirements.bytes = macho_cs.Entitlements.build(requirements.data) requirements.length = len(requirements.bytes) + 8 # then rebuild the whole data, but just to show the digest...? # requirements_data = macho_cs.Blob_.build(requirements) # log.debug(hashlib.sha1(requirements_data).hexdigest()) 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 + code_directory.data.nSpecialSlots 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, code_directory) >= 0 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, code_directory) code_directory.data.hashes[index] = slot.get_hash(hash_algorithm) 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() hash_size_sha_mapping = { 32: 'sha256', 20: 'sha1' } for i, code_directory in enumerate(cd): hash_algorithm = hash_size_sha_mapping.get(code_directory.data.hashSize) if self.has_codedirectory_slot(EntitlementsSlot, code_directory): self.fill_codedirectory_slot(EntitlementsSlot(self), code_directory, hash_algorithm) if self.has_codedirectory_slot(ResourceDirSlot, code_directory): self.fill_codedirectory_slot(ResourceDirSlot(seal_path), code_directory, hash_algorithm) if self.has_codedirectory_slot(RequirementsSlot, code_directory): self.fill_codedirectory_slot(RequirementsSlot(self), code_directory, hash_algorithm) 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) if self.has_codedirectory_slot(DerEntitlementsSlot, code_directory): self.fill_codedirectory_slot(DerEntitlementsSlot(self), 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:") 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) 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) sigwrapper.data = construct.Container(data=sig) # signer._log_parsed_asn1(sig) # sigwrapper.data = construct.Container(data="hahaha") sigwrapper.length = len(sigwrapper.data.data) + 8 sigwrapper.bytes = sigwrapper.data.data # log.debug(len(sigwrapper.bytes)) def update_offsets(self): # update section offsets, to account for any length changes offset = self.construct.data.BlobIndex[0].offset for blob in self.construct.data.BlobIndex: blob.offset = offset 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 self.construct.bytes = superblob def resign(self, bundle, signer): """ Do the actual signing. Create the structre and then update all the byte offsets """ codedirs = self.get_blobs('CSMAGIC_CODEDIRECTORY', min_expected=1, max_expected=2) # TODO - the way entitlements are handled is a code smell # 1 - We're doing a hasattr to detect whether it's a top-level app. isinstance(App, bundle) ? # 2 - unlike the seal_path and info_path, the entitlements_path is not functional. Apps are verified # based on the entitlements encoded into the code signature and slots and MAYBE the pprof. # Possible refactor - make entitlements data part of Signer rather than Bundle? if hasattr(bundle, 'entitlements_path') and bundle.entitlements_path is not None: self.set_entitlements(bundle.entitlements_path) self.set_requirements(signer) # See docs/codedirectory.rst for some notes on optional hashes self.set_codedirectories(bundle.seal_path, bundle.info_path, signer) self.set_signature(signer) self.update_offsets() # TODO make this optional, in case we want to check hashes or something # log.debug(hashes) # cd = codesig_cons.data.BlobIndex[0].blob.data # end_offset = arch_macho.macho_start + cd.codeLimit # start_offset = ((end_offset + 0xfff) & ~0xfff) - (cd.nCodeSlots * 0x1000) # for i in xrange(cd.nSpecialSlots): # expected = cd.hashes[i] # log.debug("special exp=%s" % expected.encode('hex')) # for i in xrange(cd.nCodeSlots): # expected = cd.hashes[cd.nSpecialSlots + i] # f.seek(start_offset + 0x1000 * i) # actual_data = f.read(min(0x1000, end_offset - f.tell())) # actual = hashlib.sha1(actual_data).digest() # log.debug('[%s] exp=%s act=%s' % () # ('bad', 'ok ')[expected == actual], # expected.encode('hex'), # actual.encode('hex') # ) ================================================ FILE: isign/der_encoder.py ================================================ import six from pyasn1.codec.der import encoder from pyasn1.type import univ from pyasn1.type import char def encode(element): der_object = _turn_into_der_structure(element) return encoder.encode(der_object) def _turn_into_der_structure(element): if isinstance(element, dict): set_element = univ.SetOf() for set_index, (element_key,element_value) in enumerate(six.iteritems(element)): entry_sequence = univ.Sequence() entry_sequence[0] = _turn_into_der_structure(element_key) entry_sequence[1] = _turn_into_der_structure(element_value) set_element[set_index] = entry_sequence return set_element elif isinstance(element, list): sequence_element = univ.Sequence() for sequence_index, element_item in enumerate(element): sequence_element[sequence_index] = _turn_into_der_structure(element_item) return sequence_element elif isinstance(element, bool): return univ.Boolean(element) elif isinstance(element, int): return univ.Integer(element) elif isinstance(element, str): return char.UTF8String(element) elif isinstance(element, bytes): return univ.OctetString(element) elif element is None: return univ.Null() else: raise ValueError('Unsupported type for DER: {}'.format(type(element))) ================================================ FILE: isign/exceptions.py ================================================ # -*- coding: utf-8 -*- """ Some common exceptions """ class NotSignable(Exception): """ superclass for any reason why app shouldn't be signable """ pass class NotMatched(Exception): """ thrown if we can't find any app class for this file path """ pass class MissingHelpers(NotSignable): """ thrown if helper apps are missing """ pass class MissingCredentials(Exception): """ thrown if credentials are missing """ pass class ImproperCredentials(Exception): """ thrown if something looks fishy about credentials """ pass class OpenSslFailure(Exception): """ something is wrong with openssl output """ pass ================================================ FILE: isign/isign.py ================================================ # -*- coding: utf-8 -*- import archive # import makesig import exceptions import os from os.path import dirname, exists, join, realpath # this comes with the repo PACKAGE_ROOT = dirname(realpath(__file__)) DEFAULT_APPLE_CERT_PATH = join(PACKAGE_ROOT, 'apple_credentials', 'applecerts.pem') DEFAULT_CREDENTIAL_FILE_NAMES = { 'certificate': 'certificate.pem', 'key': 'key.pem', 'provisioning_profile': 'isign.mobileprovision' } class NotSignable(Exception): """ This is just so we don't expose other sorts of exceptions """ pass def get_credential_paths(directory, file_names=DEFAULT_CREDENTIAL_FILE_NAMES): """ Given a directory, return dict of paths to standard credential files """ paths = {} for (k, file_name) in file_names.iteritems(): paths[k] = join(directory, file_name) return paths # We will default to using credentials in a particular # directory with well-known names. This is complicated because # the old way at Sauce Labs (pre-2017) was: # ~/isign-credentials/mobdev.cert.pem, etc. # But the new way that everyone should now use: # ~/.isign/certificate.pem, etc. if exists(join(os.environ['HOME'], 'isign-credentials')): DEFAULT_CREDENTIAL_PATHS = get_credential_paths( join(os.environ['HOME'], 'isign-credentials'), { 'certificate': 'mobdev.cert.pem', 'key': 'mobdev.key.pem', 'provisioning_profile': 'mobdev1.mobileprovision' } ) else: DEFAULT_CREDENTIAL_PATHS = get_credential_paths( join(os.environ['HOME'], '.isign') ) def resign_with_creds_dir(input_path, credentials_directory, **kwargs): """ Do isign.resign(), but with credential files from this directory """ kwargs.update(get_credential_paths(credentials_directory)) return resign(input_path, **kwargs) def resign(input_path, apple_cert=DEFAULT_APPLE_CERT_PATH, certificate=DEFAULT_CREDENTIAL_PATHS['certificate'], key=DEFAULT_CREDENTIAL_PATHS['key'], provisioning_profile=DEFAULT_CREDENTIAL_PATHS['provisioning_profile'], output_path=join(os.getcwd(), "out"), info_props=None, alternate_entitlements_path=None): """ Mirrors archive.resign(), put here for convenience, to unify exceptions, and to omit default args """ try: return archive.resign(input_path, certificate, key, apple_cert, provisioning_profile, output_path, info_props, alternate_entitlements_path) except exceptions.NotSignable as e: # re-raise the exception without exposing internal # details of how it happened raise NotSignable(e) def view(input_path): """ Obtain information about the app """ try: return archive.view(input_path) except exceptions.NotSignable as e: raise NotSignable(e) ================================================ FILE: isign/macho.py ================================================ # -*- coding: utf-8 -*- # # Constructs to represent various structures # in a Mach-O binary. # # As with all Constructs, can be used for both # parsing or emitting (aka building) # from construct import * from macho_cs import Blob UInt32 = ULInt32 UInt64 = ULInt64 CodeSigRef = Struct("codesig", UInt32('dataoff'), UInt32('datasize'), ) Segment = Struct('segment', PaddedStringAdapter(Bytes('segname', 16)), UInt32('vmaddr'), UInt32('vmsize'), UInt32('fileoff'), UInt32('filesize'), UInt32('maxprot'), UInt32('initprot'), UInt32('nsects'), UInt32('flags'), Rename("sections", Array(lambda ctx: ctx['nsects'], Struct('Section', PaddedStringAdapter(Bytes('sectname', 16)), PaddedStringAdapter(Bytes('segname', 16)), UInt64('addr'), UInt64('size'), UInt32('offset'), UInt32('align'), UInt32('reloff'), UInt32('nreloc'), UInt32('flags'), UInt32('reserved1'), UInt32('reserved2'), UInt32('reserved3'), ))), ) Segment64 = Struct('segment', PaddedStringAdapter(Bytes('segname', 16)), UInt64('vmaddr'), UInt64('vmsize'), UInt64('fileoff'), UInt64('filesize'), UInt32('maxprot'), UInt32('initprot'), UInt32('nsects'), UInt32('flags'), Rename("sections", Array(lambda ctx: ctx['nsects'], Struct('Section', PaddedStringAdapter(Bytes('sectname', 16)), PaddedStringAdapter(Bytes('segname', 16)), UInt64('addr'), UInt64('size'), UInt32('offset'), UInt32('align'), UInt32('reloff'), UInt32('nreloc'), UInt32('flags'), UInt32('reserved1'), UInt32('reserved2'), UInt32('reserved3'), ))), ) LoadCommand = Struct("LoadCommand", Enum(UInt32("cmd"), LC_SEGMENT=0x1, LC_SYMTAB=0x2, LC_SYMSEG=0x3, LC_THREAD=0x4, LC_UNIXTHREAD=0x5, LC_LOADFVMLIB=0x6, LC_IDFVMLIB=0x7, LC_IDENT=0x8, LC_FVMFILE=0x9, LC_PREPAGE=0xa, LC_DYSYMTAB=0xb, LC_LOAD_DYLIB=0xc, LC_ID_DYLIB=0xd, LC_LOAD_DYLINKER=0xe, LC_ID_DYLINKER=0xf, LC_PREBOUND_DYLIB=0x10, LC_ROUTINES=0x11, LC_SUB_FRAMEWORK=0x12, LC_SUB_UMBRELLA=0x13, LC_SUB_CLIENT=0x14, LC_SUB_LIBRARY=0x15, LC_TWOLEVEL_HINTS=0x16, LC_PREBIND_CKSUM=0x17, LC_LOAD_WEAK_DYLIB=0x80000018, LC_SEGMENT_64=0x19, LC_ROUTINES_64=0x1a, LC_UUID=0x1b, LC_RPATH=0x8000001c, LC_CODE_SIGNATURE=0x1d, LC_SEGMENT_SPLIT_INFO=0x1e, LC_REEXPORT_DYLIB=0x8000001f, LC_LAZY_LOAD_DYLIB=0x20, LC_ENCRYPTION_INFO=0x21, LC_DYLD_INFO=0x22, LC_DYLD_INFO_ONLY=0x80000022, LC_LOAD_UPWARD_DYLIB=0x80000023, LC_VERSION_MIN_MACOSX=0x24, LC_VERSION_MIN_IPHONEOS=0x25, LC_FUNCTION_STARTS=0x26, LC_DYLD_ENVIRONMENT=0x27, LC_MAIN=0x80000028, LC_DATA_IN_CODE=0x29, LC_SOURCE_VERSION=0x2a, LC_DYLIB_CODE_SIGN_DRS=0x2b, LC_ENCRYPTION_INFO_64=0x2c, LC_LINKER_OPTION=0x2d, LC_LINKER_OPTIMIZATION_HINT=0x2e, LC_VERSION_MIN_TVOS=0x2f, LC_VERSION_MIN_WATCHOS=0x30, LC_NOTE=0x31, LC_BUILD_VERSION=0x32, LC_DYLD_EXPORTS_TRIE = 0x80000033, LC_DYLD_CHAINED_FIXUPS = 0x80000034, LC_FILESET_ENTRY = 0x80000035 ), UInt32("cmdsize"), Peek(Switch("data", lambda ctx: ctx['cmd'], {'LC_SEGMENT': Struct('segment', PaddedStringAdapter(Bytes('segname', 16)), UInt32('vmaddr'), UInt32('vmsize'), UInt32('fileoff'), UInt32('filesize'), UInt32('maxprot'), UInt32('initprot'), UInt32('nsects'), UInt32('flags'), Rename("sections", Array(lambda ctx: ctx['nsects'], Struct('Section', PaddedStringAdapter(Bytes('sectname', 16)), PaddedStringAdapter(Bytes('segname', 16)), UInt32('addr'), UInt32('size'), UInt32('offset'), UInt32('align'), UInt32('reloff'), UInt32('nreloc'), UInt32('flags'), UInt32('reserved1'), UInt32('reserved2'), ))), ), 'LC_SEGMENT_64': Segment64, 'LC_DYLIB_CODE_SIGN_DRS': Struct("codesign_drs", UInt32('dataoff'), UInt32('datasize'), Pointer(lambda ctx: ctx['_']['_']['macho_start'] + ctx['dataoff'], Blob), ), 'LC_CODE_SIGNATURE': Struct("codesig", UInt32('dataoff'), UInt32('datasize'), Pointer(lambda ctx: ctx['_']['_']['macho_start'] + ctx['dataoff'], Blob), ), }, default=Pass)), OnDemand(Bytes('bytes', lambda ctx: ctx['cmdsize'] - 8)), #Probe(), ) MachO = Struct("MachO", Anchor("macho_start"), Enum(UInt32("magic"), MH_MAGIC=0xfeedface, MH_MAGIC_64=0xfeedfacf, MH_CIGAM_64=0xcffaedfe, ), UInt32("cputype"), UInt32("cpusubtype"), Enum(UInt32("filetype"), MH_OBJECT=0x1, MH_EXECUTE=0x2, MH_FVMLIB=0x3, MH_CORE=0x4, MH_PRELOAD=0x5, MH_DYLIB=0x6, MH_DYLINKER=0x7, MH_BUNDLE=0x8, MH_DYLIB_STUB=0x9, MH_DSYM=0xa, MH_KEXT_BUNDLE=0xb, _default_=Pass, ), UInt32("ncmds"), UInt32("sizeofcmds"), FlagsEnum(UInt32("flags"), MH_NOUNDEFS=0x1, MH_INCRLINK=0x2, MH_DYLDLINK=0x4, MH_BINDATLOAD=0x8, MH_PREBOUND=0x10, MH_SPLIT_SEGS=0x20, MH_LAZY_INIT=0x40, MH_TWOLEVEL=0x80, MH_FORCE_FLAT=0x100, MH_NOMULTIDEFS=0x200, MH_NOFIXPREBINDING=0x400, MH_PREBINDABLE=0x800, MH_ALLMODSBOUND=0x1000, MH_SUBSECTIONS_VIA_SYMBOLS=0x2000, MH_CANONICAL=0x4000, MH_WEAK_DEFINES=0x8000, MH_BINDS_TO_WEAK=0x10000, MH_ALLOW_STACK_EXECUTION=0x20000, MH_ROOT_SAFE=0x40000, MH_SETUID_SAFE=0x80000, MH_NO_REEXPORTED_DYLIBS=0x100000, MH_PIE=0x200000, MH_DEAD_STRIPPABLE_DYLIB=0x400000, MH_HAS_TLV_DESCRIPTORS=0x00800000, MH_NO_HEAP_EXECUTION=0x01000000, MH_APP_EXTENSION_SAFE=0x02000000, MH_UNUSED_1=0x04000000, MH_UNUSED_2=0x08000000, MH_UNUSED_3=0x10000000, MH_UNUSED_4=0x20000000, MH_UNUSED_5=0x40000000, MH_UNUSED_6=0x80000000, ), If(lambda ctx: ctx['magic'] in ('MH_MAGIC_64', 'MH_CIGAM_64'), UInt32('reserved')), Rename('commands', Array(lambda ctx: ctx['ncmds'], LoadCommand))) FatArch = Struct("FatArch", UBInt32("cputype"), UBInt32("cpusubtype"), UBInt32("offset"), UBInt32("size"), UBInt32("align"), Pointer(lambda ctx: ctx['offset'], MachO), ) Fat = Struct("Fat", Const(UBInt32("magic"), 0xcafebabe), UBInt32("nfat_arch"), Array(lambda ctx: ctx['nfat_arch'], FatArch), ) MachoFile = Struct("MachoFile", Peek(UInt32("magic")), Switch("data", lambda ctx: ctx['magic'], {0xfeedface: MachO, 0xfeedfacf: MachO, 0xcffaedfe: MachO, 0xcafebabe: Fat, 0xbebafeca: Fat, 0xc10cdefa: Blob, }) ) ================================================ FILE: isign/macho_cs.py ================================================ # -*- coding: utf-8 -*- # # This is a Construct library which represents an # LC_CODE_SIGNATURE structure. Like all Construct # libraries, can be used for parsing or emitting # (Construct calls it 'building') # from construct import * import plistlib class PlistAdapter(Adapter): def _encode(self, obj, context): return plistlib.writePlistToString(obj) def _decode(self, obj, context): return plistlib.readPlistFromString(obj) # talk about overdesign. # magic is in the blob struct Expr = LazyBound("expr", lambda: Expr_) Blob = LazyBound("blob", lambda: Blob_) Hashes = LazyBound("hashes", lambda: Hashes_) Hashes_ = Array(lambda ctx: ctx['nSpecialSlots'] + ctx['nCodeSlots'], Bytes("hash", lambda ctx: ctx['hashSize'])) CodeDirectory = Struct("CodeDirectory", Anchor("cd_start"), UBInt32("version"), UBInt32("flags"), UBInt32("hashOffset"), UBInt32("identOffset"), UBInt32("nSpecialSlots"), UBInt32("nCodeSlots"), UBInt32("codeLimit"), UBInt8("hashSize"), UBInt8("hashType"), UBInt8("spare1"), UBInt8("pageSize"), UBInt32("spare2"), Pointer(lambda ctx: ctx['cd_start'] - 8 + ctx['identOffset'], CString('ident')), If(lambda ctx: ctx['version'] >= 0x20100, UBInt32("scatterOffset")), If(lambda ctx: ctx['version'] >= 0x20200, UBInt32("teamIDOffset")), If(lambda ctx: ctx['version'] >= 0x20200, Pointer(lambda ctx: ctx['cd_start'] - 8 + ctx['teamIDOffset'], CString('teamID'))), If(lambda ctx: ctx['version'] >= 0x20300, UBInt32("spare3")), If(lambda ctx: ctx['version'] >= 0x20300, UBInt64("codeLimit64")), If(lambda ctx: ctx['version'] >= 0x20400,UBInt64("execSegBase")), If(lambda ctx: ctx['version'] >= 0x20400,UBInt64("execSegLimit")), If(lambda ctx: ctx['version'] >= 0x20400,UBInt64("execSegFlags")), Pointer(lambda ctx: ctx['cd_start'] - 8 + ctx['hashOffset'] - ctx['hashSize'] * ctx['nSpecialSlots'], Hashes) ) Data = Struct("Data", UBInt32("length"), Bytes("data", lambda ctx: ctx['length']), Padding(lambda ctx: -ctx['length'] & 3), ) CertSlot = Enum(UBInt32("slot"), anchorCert=-1, leafCert=0, _default_=Pass, ) Match = Struct("Match", Enum(UBInt32("matchOp"), matchExists=0, matchEqual=1, matchContains=2, matchBeginsWith=3, matchEndsWith=4, matchLessThan=5, matchGreaterThan=6, matchLessEqual=7, matchGreaterEqual=8, ), If(lambda ctx: ctx['matchOp'] != 'matchExists', Data) ) expr_args = { 'opIdent': Data, 'opAnchorHash': Sequence("AnchorHash", CertSlot, Data), 'opInfoKeyValue': Data, 'opAnd': Sequence("And", Expr, Expr), 'opOr': Sequence("Or", Expr, Expr), 'opNot': Expr, 'opCDHash': Data, 'opInfoKeyField': Sequence("InfoKeyField", Data, Match), 'opEntitlementField': Sequence("EntitlementField", Data, Match), 'opCertField': Sequence("CertField", CertSlot, Data, Match), 'opCertGeneric': Sequence("CertGeneric", CertSlot, Data, Match), 'opTrustedCert': CertSlot, } Expr_ = Struct("Expr", Enum(UBInt32("op"), opFalse=0, opTrue=1, opIdent=2, opAppleAnchor=3, opAnchorHash=4, opInfoKeyValue=5, opAnd=6, opOr=7, opCDHash=8, opNot=9, opInfoKeyField=10, opCertField=11, opTrustedCert=12, opTrustedCerts=13, opCertGeneric=14, opAppleGenericAnchor=15, opEntitlementField=16, ), Switch("data", lambda ctx: ctx['op'], expr_args, default=Pass), ) Requirement = Struct("Requirement", Const(UBInt32("kind"), 1), Expr, ) Entitlement = Struct("Entitlement", # actually a plist PlistAdapter(Bytes("data", lambda ctx: ctx['_']['length'] - 8)), ) DerEntitlement = Struct("DerEntitlement", # actually a plist in binary format Bytes("data", lambda ctx: ctx['_']['length'] - 8), ) EntitlementsBlobIndex = Struct("BlobIndex", Enum(UBInt32("type"), kSecHostRequirementType=1, kSecGuestRequirementType=2, kSecDesignatedRequirementType=3, kSecLibraryRequirementType=4, ), UBInt32("offset"), Pointer(lambda ctx: ctx['_']['sb_start'] - 8 + ctx['offset'], Blob), ) Entitlements = Struct("Entitlements", # actually a kind of super blob Anchor("sb_start"), UBInt32("count"), Array(lambda ctx: ctx['count'], EntitlementsBlobIndex), ) BlobWrapper = Struct("BlobWrapper", OnDemand(Bytes("data", lambda ctx: ctx['_']['length'] - 8)), ) BlobIndex = Struct("BlobIndex", UBInt32("type"), UBInt32("offset"), If(lambda ctx: ctx['offset'], Pointer(lambda ctx: ctx['_']['sb_start'] - 8 + ctx['offset'], Blob)), ) SuperBlob = Struct("SuperBlob", Anchor("sb_start"), UBInt32("count"), Array(lambda ctx: ctx['count'], BlobIndex), ) Blob_ = Struct("Blob", Enum(UBInt32("magic"), CSMAGIC_REQUIREMENT=0xfade0c00, CSMAGIC_REQUIREMENTS=0xfade0c01, CSMAGIC_CODEDIRECTORY=0xfade0c02, CSMAGIC_ENTITLEMENT=0xfade7171, # actually, this is kSecCodeMagicEntitlement, and not defined in the C version CSMAGIC_DER_ENTITLEMENT=0xfade7172, CSMAGIC_BLOBWRAPPER=0xfade0b01, # and this isn't even defined in libsecurity_codesigning; it's in _utilities CSMAGIC_EMBEDDED_SIGNATURE=0xfade0cc0, CSMAGIC_DETACHED_SIGNATURE=0xfade0cc1, CSMAGIC_CODE_SIGN_DRS=0xfade0c05, _default_=Pass, ), UBInt32("length"), Peek(Switch("data", lambda ctx: ctx['magic'], {'CSMAGIC_REQUIREMENT': Requirement, 'CSMAGIC_REQUIREMENTS': Entitlements, 'CSMAGIC_CODEDIRECTORY': CodeDirectory, 'CSMAGIC_ENTITLEMENT': Entitlement, 'CSMAGIC_DER_ENTITLEMENT': DerEntitlement, 'CSMAGIC_BLOBWRAPPER': BlobWrapper, 'CSMAGIC_EMBEDDED_SIGNATURE': SuperBlob, 'CSMAGIC_DETACHED_SIGNATURE': SuperBlob, 'CSMAGIC_CODE_SIGN_DRS': SuperBlob, })), OnDemand(Bytes('bytes', lambda ctx: ctx['length'] - 8)), ) ================================================ FILE: isign/makesig.py ================================================ # -*- coding: utf-8 -*- # Library to construct an LC_CODE_SIGNATURE construct # from scratch. Does not work yet. # # Abandoned development May 2015 when it became clear that most # apps that were uploaded to us would already be signed. But # we may need this someday, so preserving here. # import plistlib import io import construct import hashlib import logging import math import macho import macho_cs import utils import der_encoder log = logging.getLogger(__name__) def make_arg(data_type, arg): if data_type.name == 'Data': return construct.Container(data=arg, length=len(arg)) elif data_type.name.lower() == 'expr': if isinstance(arg, construct.Container): # preserve expressions that are already containerized return arg return make_expr(*arg) elif data_type.name == 'slot': if arg == 'leafCert': return 0 return arg elif data_type.name == 'Match': matchOp = arg[0] data = None if len(arg) > 1: data = construct.Container(data=arg[1], length=len(arg[1])) return construct.Container(matchOp=matchOp, Data=data) log.debug(data_type) log.debug(data_type.name) log.debug(arg) assert 0 def make_expr(op, *args): full_op = "op" + op data = None data_type = macho_cs.expr_args.get(full_op) if isinstance(data_type, macho_cs.Sequence): if len(data_type.subcons) == len(args): data = [make_arg(dt, arg) for dt, arg in zip(data_type.subcons, args)] else: # automatically nest binary operations to accept >2 args data = [make_arg(data_type.subcons[0], args[0]), make_expr(op, *args[1:])] elif data_type: data = make_arg(data_type, args[0]) return construct.Container(op=full_op, data=data) def make_requirements(drs, ident, common_name): expr = make_expr( 'And', ('Ident', ident), ('AppleGenericAnchor',), ('CertField', 'leafCert', 'subject.CN', ['matchEqual', common_name]), ('CertGeneric', 1, '*\x86H\x86\xf7cd\x06\x02\x01', ['matchExists'])) des_req = construct.Container(kind=1, expr=expr) des_req_data = macho_cs.Requirement.build(des_req) reqs = construct.Container( sb_start=0, count=1, BlobIndex=[construct.Container(type='kSecDesignatedRequirementType', offset=28, blob=construct.Container(magic='CSMAGIC_REQUIREMENT', length=len(des_req_data) + 8, data=des_req, bytes=des_req_data))]) if drs: dr_exprs = [] for dr in drs.data.BlobIndex: if dr.blob is not None: dr_exprs.append(dr.blob.data.expr) # make_expr expects at least 2 arguments, need to verify that we pass those in, otherwise just return if len(dr_exprs) > 1: expr = make_expr('Or', *dr_exprs) lib_req = construct.Container(kind=1, expr=expr) lib_req_data = macho_cs.Requirement.build(lib_req) reqs.BlobIndex.append(construct.Container(type='kSecLibraryRequirementType', offset=28 + len(des_req_data) + 8, blob=construct.Container(magic='CSMAGIC_REQUIREMENT', length=len(lib_req_data) + 8, data=lib_req, bytes=lib_req_data))) reqs.count += 1 return reqs def build_code_directory_blob(hash_algorithm, teamID, ident_for_signature, code_limit, hashes, exec_segment_offset, exec_segment_limit, is_main_binary): if hash_algorithm == 'sha1': hash_type_value = 1 hash_size = 20 elif hash_algorithm == 'sha256': hash_type_value = 2 hash_size = 32 else: raise ValueError("Incorrect hash type provided: {}".format(hash_algorithm)) for hash in hashes: if len(hash) != hash_size: raise Exception('Incorrect hash {} for length {} ({})'.format(hash, hash_size, len(hash))) empty_hash = "\x00" * hash_size special_slots_length = 7 # The length of the fields in the CodeDirectory is at least these fiels which are always present. # CD Magic (4) # length (4) # version (4) # flags (4) # hashOffset (4) # identOffset (4) # nSpecialSlots (4) # nCodeSlots (4) # codeLimit (4) # hashSize (1) # hashType (1) # spare1 (1) # pageSize (1) # spare (4) # scatterOffset (4) # teamIDOffset (4) # spare3 (4) # codeLimit64 (8) # execSegBase (8) # execSegLimit (8) # execSegFlags (8) # which in total are 88 FIXED_FIELDS_SIZE = 88 cd = construct.Container(cd_start=None, version=0x20400, flags=0, identOffset= FIXED_FIELDS_SIZE, nSpecialSlots=special_slots_length, nCodeSlots=len(hashes), codeLimit=code_limit, hashSize=hash_size, hashType=hash_type_value, spare1=0, pageSize=12, # Page size is indicated as a log in base 2. The size is 0x1000 = 2 ^ 12 spare2=0, ident=ident_for_signature, scatterOffset=0, teamIDOffset= FIXED_FIELDS_SIZE + len(ident_for_signature), teamID=teamID, hashOffset= FIXED_FIELDS_SIZE + (hash_size * special_slots_length) + len(ident_for_signature) + len(teamID), hashes=([empty_hash] * special_slots_length) + hashes, spare3=0, codeLimit64=0, # 0 means fallback to codeLimit execSegBase=exec_segment_offset, execSegLimit=exec_segment_limit, execSegFlags=1 if is_main_binary else 0, ) return cd def make_basic_codesig(entitlements_file, drs, code_limit, hashes_sha1, hashes_sha256, signer, ident, exec_segment_offset, exec_segment_limit, is_main_binary): common_name = signer.get_common_name() log.debug("ident: {}".format(ident)) log.debug("codelimit: {}".format(code_limit)) teamID = signer._get_team_id() + '\x00' ident_for_signature = ident + '\x00' cd = build_code_directory_blob( hash_algorithm='sha1', teamID=teamID, ident_for_signature=ident_for_signature, code_limit=code_limit, hashes=hashes_sha1, exec_segment_offset=exec_segment_offset, exec_segment_limit=exec_segment_limit, is_main_binary=is_main_binary) cd_data = macho_cs.CodeDirectory.build(cd) # SHA256 codesignature disabled for now, as it is not correctly implemented and fails on iOS 15.1. For now we are # just adding this switch so we can enable it again once it is properly done. At least, the current signature is not # including a signed attribute with OID 1.2.840.113635.100.9.1 containing a plist with a "cdhashes" list (on which # it has the base64 of the SHA1 and first 20 bytes of SHA256). That missing part is probably what is missing. sha256_codesignature_enabled = False # Superblob has # magic (4) # size (4) # num of blobs (4) # [blob[n], offset to n ] (4 + 4) repeated for each blob number_of_blobs = 3 if sha256_codesignature_enabled: number_of_blobs = 4 if entitlements_file != None: number_of_blobs += 2 offset = 4 + 4 + 4 + (8 * number_of_blobs) cd_index = construct.Container(type=0, offset=offset, blob=construct.Container(magic='CSMAGIC_CODEDIRECTORY', length=len(cd_data) + 8, data=cd, bytes=cd_data, )) offset += cd_index.blob.length reqs_sblob = make_requirements(drs, ident, common_name) reqs_sblob_data = macho_cs.Entitlements.build(reqs_sblob) requirements_index = construct.Container(type=2, offset=offset, blob=construct.Container(magic='CSMAGIC_REQUIREMENTS', length=len(reqs_sblob_data) + 8, data="", bytes=reqs_sblob_data, )) offset += requirements_index.blob.length entitlements_index = None der_entitlements_index = None if entitlements_file != None: entitlements_bytes = open(entitlements_file, "rb").read() entitlements_index = construct.Container(type=5, offset=offset, blob=construct.Container(magic='CSMAGIC_ENTITLEMENT', length=len(entitlements_bytes) + 8, data="", bytes=entitlements_bytes )) offset += entitlements_index.blob.length xml_entitlements_dict = plistlib.readPlist(io.BytesIO(entitlements_bytes)) der_entitlements_bytes = der_encoder.encode(xml_entitlements_dict) der_entitlements_index = construct.Container(type=7, offset=offset, blob=construct.Container(magic='CSMAGIC_DER_ENTITLEMENT', length=len(der_entitlements_bytes) + 8, data="", bytes=der_entitlements_bytes )) offset += der_entitlements_index.blob.length cd_sha256_index = None if sha256_codesignature_enabled: cd_sha256 = build_code_directory_blob( hash_algorithm='sha256', teamID=teamID, ident_for_signature=ident_for_signature, code_limit=code_limit, hashes=hashes_sha256, exec_segment_offset=exec_segment_offset, exec_segment_limit=exec_segment_limit, is_main_binary=is_main_binary) cd_sha256_data = macho_cs.CodeDirectory.build(cd_sha256) cd_sha256_index = construct.Container(type=0x1000, offset=offset, blob=construct.Container(magic='CSMAGIC_CODEDIRECTORY', length=len(cd_sha256_data) + 8, data=cd_sha256, bytes=cd_sha256_data, )) offset += cd_sha256_index.blob.length sigwrapper_index = construct.Container(type=65536, offset=offset, blob=construct.Container(magic='CSMAGIC_BLOBWRAPPER', length=0 + 8, data="", bytes="", )) indicies = filter(None, [cd_index, requirements_index, entitlements_index, der_entitlements_index, cd_sha256_index, sigwrapper_index]) superblob = construct.Container( sb_start=0, count=len(indicies), BlobIndex=indicies) data = macho_cs.SuperBlob.build(superblob) chunk = macho_cs.Blob.build(construct.Container( magic="CSMAGIC_EMBEDDED_SIGNATURE", length=len(data) + 8, data=data, bytes=data)) return macho_cs.Blob.parse(chunk) def make_signature(arch_macho, arch_offset, arch_size, cmds, f, entitlements_file, codesig_data_length, signer, ident): # NB: arch_offset is absolute in terms of file start. Everything else is relative to arch_offset! # sign from scratch log.debug("signing from scratch") drs = None drs_lc = cmds.get('LC_DYLIB_CODE_SIGN_DRS') if drs_lc: drs = drs_lc.data.blob codesig_offset = utils.round_up(arch_size, 16) # generate code hashes log.debug("codesig offset: {}".format(codesig_offset)) codeLimit = codesig_offset log.debug("new cL: {}".format(hex(codeLimit))) nCodeSlots = int(math.ceil(float(codesig_offset) / 0x1000)) log.debug("new nCS: {}".format(nCodeSlots)) # generate placeholder LC_CODE_SIGNATURE (like what codesign_allocate does) fake_hashes_sha1 = ["\x00" * 20] * nCodeSlots fake_hashes_sha256 = ["\x00" * 32] * nCodeSlots # Initially set to 0 (for fake signature, later on populated). exec_segment_found = False exec_segment_offset = 0 exec_segment_limit = 0 is_main_binary = 'MH_EXECUTE' in arch_macho.filetype log.debug("is_main_binary: {}".format(nCodeSlots)) codesig_cons = make_basic_codesig(entitlements_file, drs, codeLimit, fake_hashes_sha1, fake_hashes_sha256, signer, ident, exec_segment_offset, exec_segment_limit, is_main_binary) codesig_data = macho_cs.Blob.build(codesig_cons) cmd_data = construct.Container(dataoff=codesig_offset, datasize=codesig_data_length) cmd = construct.Container(cmd='LC_CODE_SIGNATURE', cmdsize=16, data=cmd_data, bytes=macho.CodeSigRef.build(cmd_data)) log.debug("CS blob before: {}".format(utils.print_structure(codesig_cons, macho_cs.Blob))) log.debug("len(codesig_data): {}".format(len(codesig_data))) codesig_length = codesig_data_length log.debug("codesig length: {}".format(codesig_length)) log.debug("old ncmds: {}".format(arch_macho.ncmds)) arch_macho.ncmds += 1 log.debug("new ncmds: {}".format(arch_macho.ncmds)) log.debug("old sizeofcmds: {}".format(arch_macho.sizeofcmds)) arch_macho.sizeofcmds += cmd.cmdsize log.debug("new sizeofcmds: {}".format(arch_macho.sizeofcmds)) arch_macho.commands.append(cmd) hashes_sha1 = [] hashes_sha256 = [] if codesig_data_length > 0: # Patch __LINKEDIT for lc in arch_macho.commands: if lc.cmd == 'LC_SEGMENT_64' or lc.cmd == 'LC_SEGMENT': if (not exec_segment_found) and lc.data.segname == '__TEXT': # Exec segment offset and limit refer to the first text segment. exec_segment_offset = lc.data.fileoff exec_segment_limit = lc.data.filesize exec_segment_found = True log.debug('Exec segment found: Offset:{}, limit:{}'.format(exec_segment_offset, exec_segment_limit)) if lc.data.segname == '__LINKEDIT': log.debug("found __LINKEDIT, old filesize {}, vmsize {}".format(lc.data.filesize, lc.data.vmsize)) lc.data.filesize = utils.round_up(lc.data.filesize, 16) + codesig_length if (lc.data.filesize > lc.data.vmsize): lc.data.vmsize = utils.round_up(lc.data.filesize, 4096) if lc.cmd == 'LC_SEGMENT_64': lc.bytes = macho.Segment64.build(lc.data) else: lc.bytes = macho.Segment.build(lc.data) log.debug("new filesize {}, vmsize {}".format(lc.data.filesize, lc.data.vmsize)) actual_data = macho.MachO.build(arch_macho) log.debug("actual_data length with codesig LC {}".format(len(actual_data))) # Now seek to the start of the actual data and read until the end of the arch. f.seek(arch_offset + len(actual_data)) bytes_to_read = codesig_offset + arch_offset - f.tell() file_slice = f.read(bytes_to_read) if len(file_slice) < bytes_to_read: log.warn("expected {} bytes but got {}, zero padding.".format(bytes_to_read, len(file_slice))) file_slice += ("\x00" * (bytes_to_read - len(file_slice))) actual_data += file_slice for i in xrange(nCodeSlots): actual_data_slice = actual_data[(0x1000 * i):(0x1000 * i + 0x1000)] actual_sha1 = hashlib.sha1(actual_data_slice).digest() log.debug("Slot {} (File page @{} sha1): {}".format(i, hex(0x1000 * i), actual_sha1.encode('hex'))) hashes_sha1.append(actual_sha1) actual_sha256 = hashlib.sha256(actual_data_slice).digest() log.debug("Slot {} (File page @{} sha256): {}".format(i, hex(0x1000 * i), actual_sha256.encode('hex'))) hashes_sha256.append(actual_sha256) else: hashes_sha1 = fake_hashes_sha1 hashes_sha256 = fake_hashes_sha256 # Replace placeholder with real one. codesig_cons = make_basic_codesig(entitlements_file, drs, codeLimit, hashes_sha1, hashes_sha256, signer, ident, exec_segment_offset, exec_segment_limit, is_main_binary) codesig_data = macho_cs.Blob.build(codesig_cons) cmd_data = construct.Container(dataoff=codesig_offset, datasize=len(codesig_data)) cmd = construct.Container(cmd='LC_CODE_SIGNATURE', cmdsize=16, data=cmd_data, bytes=macho.CodeSigRef.build(cmd_data)) arch_macho.commands[-1] = cmd cmds['LC_CODE_SIGNATURE'] = cmd return codesig_data ================================================ FILE: isign/multisign.py ================================================ # -*- coding: utf-8 -*- from os.path import isdir import isign from archive import archive_factory from signer import Signer import logging import multiprocessing log = logging.getLogger(__name__) MAX_PROCESSES = multiprocessing.cpu_count() def resign(args): """ Given a tuple consisting of a path to an uncompressed archive, credential directory, and desired output path, resign accordingly. Returns a tuple of (cred_dir, path to resigned app) """ ua, cred_dir, resigned_path = args try: log.debug('resigning with %s %s -> %s', ua.path, cred_dir, resigned_path) # get the credential files, create the 'signer' credential_paths = isign.get_credential_paths(cred_dir) signer = Signer(signer_cert_file=credential_paths['certificate'], signer_key_file=credential_paths['key'], apple_cert_file=isign.DEFAULT_APPLE_CERT_PATH) # sign it (in place) ua.bundle.resign(signer, credential_paths['provisioning_profile']) log.debug("outputing %s", resigned_path) # and archive it there ua.archive(resigned_path) finally: ua.remove() return (cred_dir, resigned_path) def clone_ua(args): original_ua, target_ua_path = args log.debug('cloning %s to %s', original_ua.path, target_ua_path) ua = original_ua.clone(target_ua_path) log.debug('done cloning to %s', original_ua.path) return ua def multisign(original_path, cred_dirs_to_output_paths, info_props=None): """ Given a path to an app, a mapping of credential directories to desired output paths, optional info.plist properties to overwrite, produce re-signed versions of the app as desired. See: multisign_archive, which this wraps. Returns an array of tuples of [(credentials_dir, resigned app path)...] """ archive = archive_factory(original_path) if archive is None: log.debug("%s didn't look like an app...", original_path) return None return multisign_archive(archive, cred_dirs_to_output_paths, info_props) def multisign_archive(archive, cred_dirs_to_output_paths, info_props=None): """ Given an isign.archive object, a mapping of credential directories to desired output paths, optional info.plist properties to overwrite, produce re-signed versions of the IPA. If info_props are provided, it will overwrite those properties in the app's Info.plist. Returns an array of tuples of [(credentials_dir, resigned app path)...] """ # get ready for multiple processes... p = multiprocessing.Pool(MAX_PROCESSES) # ua is potentially an isign.archive.UncompressedArchive ua = None results = [] try: ua = archive.unarchive_to_temp() if info_props: # Override info.plist props ua.bundle.update_info_props(info_props) # Since the signing process rewrites files, we must first create uncompressed archives # for each credentials_directory. # The first is simply the uncompressed archive we just made uas = [ua] # But the rest need to be copied. This might take a while, so let's do it in parallel # this will copy them to /path/to/uncompressedArchive_1, .._2, and so on # and make UncompressedArchive objects that can be used for resigning target_ua_paths = [] for i in range(1, len(cred_dirs_to_output_paths)): target_ua_paths.append((ua, ua.path + '_' + str(i))) uas += p.map(clone_ua, target_ua_paths) # now we should have one UncompressedArchive for every credential directory assert len(uas) == len(cred_dirs_to_output_paths) # We will now construct arguments for all the resignings resign_args = [] for i, (cred_dir, output_path) in enumerate(cred_dirs_to_output_paths.items()): resign_args.append((uas[i], cred_dir, output_path)) log.debug('resign args: %s', resign_args) # In parallel, resign each uncompressed archive with supplied credentials, # and make archives in the desired paths. results = p.map(resign, resign_args) except isign.NotSignable as e: msg = "Not signable: <{0}>: {1}\n".format(archive.path, e) log.error(msg) raise finally: if ua is not None and isdir(ua.path): ua.remove() return results ================================================ FILE: isign/signable.py ================================================ # -*- coding: utf-8 -*- # # Represents a file that can be signed. A file that # conforms to the Mach-O ABI. # # Executable, dylib, or framework. # from abc import ABCMeta from codesig import (Codesig, EntitlementsSlot, ResourceDirSlot, RequirementsSlot, ApplicationSlot, DerEntitlementsSlot, InfoSlot) import logging import macho from makesig import make_signature import os import tempfile import utils import shutil log = logging.getLogger(__name__) class Signable(object): __metaclass__ = ABCMeta slot_classes = [] def __init__(self, bundle, path, signer): log.debug("working on {0}".format(path)) self.bundle = bundle self.path = path self.signer = signer self.f = open(self.path, "rb") self.f.seek(0, os.SEEK_END) self.file_end = self.f.tell() self.f.seek(0) self.m = macho.MachoFile.parse_stream(self.f) self.sign_from_scratch = False # may set sign_from_scratch to True self.arches = self._parse_arches() def _parse_arches(self): """ parse architectures and associated Codesig """ arch_macho = self.m.data arches = [] if 'FatArch' in arch_macho: log.debug('found fat binary') for i, arch in enumerate(arch_macho.FatArch): log.debug('found fat slice: cputype {}, cpusubtype {}'.format(arch.cputype, arch.cpusubtype)) this_arch_macho = arch.MachO log.debug('slice {}: arch offset: {}, size: {}'.format(i, arch.offset, arch.size)) arch_object = self._get_arch(this_arch_macho, arch.offset, arch.size) arch_object['fat_index'] = i arches.append(arch_object) else: log.debug('found thin binary: cputype {}, cpusubtype {}'.format(arch_macho.cputype, arch_macho.cpusubtype)) arches.append(self._get_arch(arch_macho, 0, self.file_end)) return arches def _get_arch(self, macho, arch_offset, arch_size): arch = {'macho': macho, 'arch_offset': arch_offset, 'arch_size': arch_size} arch['cmds'] = {} for cmd in macho.commands: name = cmd.cmd arch['cmds'][name] = cmd codesig_data = None if 'LC_CODE_SIGNATURE' in arch['cmds']: arch['lc_codesig'] = arch['cmds']['LC_CODE_SIGNATURE'] codesig_offset = arch['macho'].macho_start + arch['lc_codesig'].data.dataoff self.f.seek(codesig_offset) codesig_data = self.f.read(arch['lc_codesig'].data.datasize) # log.debug("codesig len: {0}".format(len(codesig_data))) else: log.info("signing from scratch!") self.sign_from_scratch = True entitlements_file = self.bundle.get_entitlements_path() # '/path/to/some/entitlements.plist' # Stage 1: Fake signature fake_codesig_data = make_signature(macho, arch_offset, arch_size, arch['cmds'], self.f, entitlements_file, 0, self.signer, self.bundle.get_info_prop('CFBundleIdentifier')) # We're stripping out the fake LC_CODE_SIGNATURE command, which we know has a size of 16, so we need to # decrement the overall sizeofcmds macho.ncmds -= 1 macho.commands = macho.commands[:-1] macho.sizeofcmds -= 16 # Get the length fake_codesig = Codesig(self, fake_codesig_data) fake_codesig.set_signature(self.signer) fake_codesig.update_offsets() fake_codesig_length = len(fake_codesig.build_data()) log.debug("fake codesig length: {}".format(fake_codesig_length)) # stage 2: real signature codesig_data = make_signature(macho, arch_offset, arch_size, arch['cmds'], self.f, entitlements_file, fake_codesig_length, self.signer, self.bundle.get_info_prop('CFBundleIdentifier')) arch['lc_codesig'] = arch['cmds']['LC_CODE_SIGNATURE'] arch['codesig'] = Codesig(self, codesig_data) arch['codesig_len'] = len(codesig_data) if self.sign_from_scratch: arch['codesig_data'] = codesig_data return arch def _sign_arch(self, arch, app, signer): # Returns slice-relative offset, code signature blob arch['codesig'].resign(app, signer) new_codesig_data = arch['codesig'].build_data() new_codesig_len = len(new_codesig_data) log.debug("new codesig len is: {0}".format(new_codesig_len)) padding_length = arch['codesig_len'] - new_codesig_len new_codesig_data += "\x00" * padding_length # log.debug("padded len: {0}".format(len(new_codesig_data))) # log.debug("----") cmd = arch['lc_codesig'] cmd.data.datasize = len(new_codesig_data) cmd.bytes = macho.CodeSigRef.build(arch['lc_codesig'].data) offset = cmd.data.dataoff return offset, new_codesig_data def should_fill_slot(self, codesig, slot): slot_class = slot.__class__ if slot_class not in self.slot_classes: # This signable does not have this slot return False if self.sign_from_scratch: return True if slot_class == InfoSlot and not self.bundle.info_props_changed(): # No Info.plist changes, don't fill return False if slot_class == ApplicationSlot and not codesig.is_sha256_signature(): # Application slot only needs to be zeroed out when there's a sha256 layer return False return True def get_changed_bundle_id(self): # Return a bundle ID to assign if Info.plist's CFBundleIdentifier value was changed if self.bundle.info_prop_changed('CFBundleIdentifier'): return self.bundle.get_info_prop('CFBundleIdentifier') else: return None def sign(self, app, signer): temp = tempfile.NamedTemporaryFile('wb', delete=False) # If signing fat binary from scratch, need special handling # TODO: we assume that if any slice is unsigned, all slices are. This should be true in practice but # we should still guard against this. if self.sign_from_scratch and 'FatArch' in self.m.data: # Fat binaries have more than 2 architectures, but thin ones only have one, so we assert that assert len(self.arches) >= 1 # todo(markwang): Update fat headers and mach_start for each slice if needewd log.debug('signing fat binary from scratch') sorted_archs = sorted(self.arches, key=lambda arch: arch['arch_offset']) prev_arch_end = 0 for arch in sorted_archs: fatentry = arch['macho'] # has pointert to container codesig_arch_offset, new_codesig_data = self._sign_arch(arch, app, signer) codesig_file_offset = arch['arch_offset'] + codesig_arch_offset log.debug('existing arch slice: cputype {}, cpusubtype {}, offset {}, size {}' .format(fatentry.cputype, fatentry.cpusubtype, arch['arch_offset'], arch['arch_size'])) log.debug("codesig arch offset: {2}, file offset: {0}, len: {1}" .format(codesig_file_offset, len(new_codesig_data), codesig_arch_offset)) assert codesig_file_offset >= (arch['arch_offset'] + arch['arch_size']) # Store the old slice offset/sizes because we need them when we copy the data slices from self.f to temp arch['old_arch_offset'] = arch['arch_offset'] arch['old_arch_size'] = arch['arch_size'] arch['codesig_arch_offset'] = codesig_arch_offset arch['codesig_data'] = new_codesig_data new_arch_size = codesig_arch_offset + len(new_codesig_data) if prev_arch_end > arch['arch_offset']: arch['arch_offset'] = utils.round_up(prev_arch_end, 16384) prev_arch_end = arch['arch_offset'] + new_arch_size arch['arch_size'] = new_arch_size log.debug('new arch slice after codesig: offset {}, size {}'.format(arch['arch_offset'], arch['arch_size'])) # write slices and code signatures in reverse order for arch in reversed(sorted_archs): self.f.seek(arch['old_arch_offset']) temp.seek(arch['arch_offset']) temp.write(self.f.read(arch['old_arch_size'])) temp.seek(arch['arch_offset'] + arch['codesig_arch_offset']) temp.write(arch['codesig_data']) fatarch_info = self.m.data.FatArch[arch['fat_index']] fatarch_info.size = arch['arch_size'] fatarch_info.offset = arch['arch_offset'] else: # copy self.f into temp, reset to beginning of file self.f.seek(0) temp.write(self.f.read()) temp.seek(0) # write new codesign blocks for each arch offset_fmt = ("offset: {2}, write offset: {0}, " "new_codesig_data len: {1}") for arch in self.arches: offset, new_codesig_data = self._sign_arch(arch, app, signer) write_offset = arch['macho'].macho_start + offset log.debug(offset_fmt.format(write_offset, len(new_codesig_data), offset)) temp.seek(write_offset) temp.write(new_codesig_data) # write new headers temp.seek(0) macho.MachoFile.build_stream(self.m, temp) temp.close() # make copy have same permissions mode = os.stat(self.path).st_mode os.chmod(temp.name, mode) # log.debug("moving temporary file to {0}".format(self.path)) shutil.move(temp.name, self.path) class Executable(Signable): """ The main executable of an app. """ slot_classes = [EntitlementsSlot, DerEntitlementsSlot, ResourceDirSlot, RequirementsSlot, ApplicationSlot, InfoSlot] class Dylib(Signable): """ A dynamic library that isn't part of its own bundle, e.g. the Swift libraries. TODO: Dylibs have an info slot, however the Info.plist is embedded in the __TEXT section of the file (__info_plist) instead of being a seperate file. Add read/write of the embedded Info.plist so we can include InfoSlot below. """ slot_classes = [EntitlementsSlot, DerEntitlementsSlot, RequirementsSlot] class Appex(Signable): """ An app extension """ slot_classes = [EntitlementsSlot, DerEntitlementsSlot, RequirementsSlot, InfoSlot] class Framework(Signable): """ The main executable of a Framework, which is a library of sorts but is bundled with both files and code """ slot_classes = [ResourceDirSlot, RequirementsSlot, InfoSlot] ================================================ FILE: isign/signer.py ================================================ # -*- coding: utf-8 -*- # # Small object that can be passed around easily, that represents # our signing credentials, and can sign data. # # Unfortunately the installed python OpenSSL library doesn't # offer what we need for cms, so we also need to shell out to the openssl # tool, and make sure it's the right version. from distutils import spawn from exceptions import (ImproperCredentials, MissingCredentials, OpenSslFailure) import logging from OpenSSL import crypto import os import os.path import subprocess import re OPENSSL = os.getenv('OPENSSL', spawn.find_executable('openssl')) # modern OpenSSL versions look like '0.9.8zd'. Use a regex to parse OPENSSL_VERSION_RE = re.compile(r'(\d+).(\d+).(\d+)(\w*)') MINIMUM_OPENSSL_VERSION = '1.0.1' log = logging.getLogger(__name__) def openssl_command(args, data=None, expect_err=False): """ Given array of args, and optionally data to write, return results of openssl command. Some commands always write something to stderr, so allow for that with the expect_err param. """ cmd = [OPENSSL] + args cmd_str = ' '.join(cmd) # log.debug('running command ' + cmd_str) proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE) if data is not None: proc.stdin.write(data) out, err = proc.communicate() if not expect_err: if err is not None and err != '': log.error("Command `{0}` returned error:\n{1}".format(cmd_str, err)) if proc.returncode != 0: msg = "openssl command `{0}` failed, see log for error".format(cmd_str) raise OpenSslFailure(msg) if expect_err: return (out, err) else: return out def get_installed_openssl_version(): version_line = openssl_command(['version']) # e.g. 'OpenSSL 0.9.8zd 8 Jan 2015' return re.split(r'\s+', version_line)[1] def is_openssl_version_ok(version, minimum): """ check that the openssl tool is at least a certain version """ version_tuple = openssl_version_to_tuple(version) minimum_tuple = openssl_version_to_tuple(minimum) return version_tuple >= minimum_tuple def openssl_version_to_tuple(s): """ OpenSSL uses its own versioning scheme, so we convert to tuple, for easier comparison """ search = re.search(OPENSSL_VERSION_RE, s) if search is not None: return search.groups() return () class Signer(object): """ collaborator, holds the keys, identifiers for signer, and knows how to sign data """ def __init__(self, signer_key_file=None, signer_cert_file=None, apple_cert_file=None, team_id=None): """ signer_key_file = your org's key .pem signer_cert_file = your org's cert .pem apple_cert_file = apple certs in .pem form team_id = your Apple Organizational Unit code """ for filename in [signer_key_file, signer_cert_file, apple_cert_file]: if not os.path.exists(filename): msg = "Can't find {0}".format(filename) log.warn(msg) raise MissingCredentials(msg) self.signer_key_file = signer_key_file self.signer_cert_file = signer_cert_file self.apple_cert_file = apple_cert_file team_id = self._get_team_id() if team_id is None: raise ImproperCredentials("Cert file does not contain Subject line" "with Apple Organizational Unit (OU)") self.team_id = team_id self.check_openssl_version() def check_openssl_version(self): openssl_version = get_installed_openssl_version() if not is_openssl_version_ok(openssl_version, MINIMUM_OPENSSL_VERSION): msg = "Signing may not work: OpenSSL version is {0}, need {1} !" log.warn(msg.format(openssl_version, MINIMUM_OPENSSL_VERSION)) def sign(self, data, digest_algorithm = "sha1"): """ sign data, return filehandle """ cmd = [ "cms", "-sign", "-binary", "-nosmimecap", "-certfile", self.apple_cert_file, "-signer", self.signer_cert_file, "-inkey", self.signer_key_file, "-keyform", "pem", "-outform", "DER", "-md", digest_algorithm ] signature = openssl_command(cmd, data) log.debug("in length: {}, out length: {}".format(len(data), len(signature))) # in some cases we've seen this return a zero length file. # Misconfigured machines? if len(signature) < 128: too_small_msg = "Command `{0}` returned success, but signature " "seems too small ({1} bytes)" raise OpenSslFailure(too_small_msg.format(' '.join(cmd), len(signature))) return signature def get_common_name(self): """ read in our cert, and get our Common Name """ with open(self.signer_cert_file, 'rb') as fh: cert = crypto.load_certificate(crypto.FILETYPE_PEM, fh.read()) subject = cert.get_subject() return dict(subject.get_components())['CN'] def _log_parsed_asn1(self, data): cmd = ['asn1parse', '-inform', 'DER' '-i'] parsed_asn1 = openssl_command(cmd) log.debug(parsed_asn1) def _get_team_id(self): """ Same as Apple Organizational Unit. Should be in the cert """ team_id = None cmd = [ 'x509', '-in', self.signer_cert_file, '-text', '-noout' ] certificate_info = openssl_command(cmd) subject_with_ou_match = re.compile(r'\s+Subject:.*OU\s?=\s?(\w+)') for line in certificate_info.splitlines(): match = subject_with_ou_match.match(line) if match is not None: team_id = match.group(1) break return team_id ================================================ FILE: isign/utils.py ================================================ # -*- coding: utf-8 -*- import binascii import re def print_data(data): hexstring = binascii.hexlify(data) n = 80 split_string = "\n".join([hexstring[i:i+n] for i in range(0, len(hexstring), n)]) print split_string def round_up(x, k): return ((x + k - 1) & -k) def print_structure(container, struct): actual_data = struct.build(container) return "{}".format(struct.parse(actual_data)) # remove control character in string # This will affect the executable_name part in path def remove_control_char(str): return re.compile('[\\x00-\\x08\\x0b-\\x0c\\x0e-\\x1f]').sub('', str) ================================================ FILE: jenkins.sh ================================================ #!/bin/bash # figure out our package name from the name of repo pushd $(dirname $0) >/dev/null package_name=$(basename $PWD) package=$(echo $package_name | sed 's/-/_/g') popd >/dev/null version=$(./version.sh) # this communicates to setup.py to use the version # number we just made, rather than generating another export PYTHON_PACKAGE_VERSION=${version} make_venv() { # For some reason mkvirtualenv returns with exit code 1 on success. So we # have to just continue. virtualenv $TMPDIR || true source $TMPDIR/bin/activate pip install -r dev/requirements.txt } build_release() { python setup.py sdist release=${package_name}-${version}.tar.gz } test_release() { rm -rf dist-release && mkdir -p dist-release echo "pip install dist/$release" pip install dist/$release echo -e "\nInstalled packages:" pip freeze -l echo mv $package ${package}.testing ./run_tests.sh mv dist/$release dist-release/ } # to push tags: add the repo to the "bots" team tag_release() { tag="v$version" echo "Tagging $head as $tag" git tag $tag $head git push origin $tag } update_pypi() { # always succeed with upload - transient errors with pypi # should not cause red build twine upload dist-release/$release || true } cleanup() { rm -rf $TMPDIR if [[ -d ${package}.testing ]]; then mv ${package}.testing $package fi } set -e TMPDIR=$(mktemp -d /tmp/${package_name}.XXXXXXXX) trap cleanup 0 make_venv build_release test_release tag_release update_pypi cleanup ================================================ FILE: run_tests.sh ================================================ #!/bin/bash -e name=$(basename $PWD) package=$(echo $name | sed 's/-/_/g') # look for required apps for app in unzip zip; do if ! which $app >/dev/null; then echo "Missing application: $app" exit 1 fi done # run test suite find . -name '*.pyc' -delete pushd tests >/dev/null version=$(python -c "import $package; print ${package}.__version__") echo "Testing $name v${version}" nosetests popd >/dev/null ================================================ FILE: setup.cfg ================================================ [metadata] description-file = README.rst ================================================ FILE: setup.py ================================================ # encoding: utf-8 from setuptools import setup, find_packages from codecs import open # to use a consistent encoding from subprocess import check_output from os import path, environ here = path.abspath(path.dirname(__file__)) if path.exists(path.join(here, "version.sh")): # development if 'PYTHON_PACKAGE_VERSION' in environ: version = environ['PYTHON_PACKAGE_VERSION'] else: version = check_output(path.join(here, "version.sh")).strip() package_name = path.basename(here) else: # source package with open(path.join(here, "PKG-INFO")) as f: for line in f.readlines(): if line.startswith("Version:"): version = line.split(":")[1].strip() elif line.startswith("Name:"): package_name = line.split(":")[1].strip() package = package_name.replace('-', '_') setup( name=package_name, version=version, description='Re-signing iOS apps without Apple tools', url='https://github.com/saucelabs/{}'.format(package_name), download_url='https://github.com/saucelabs/{}/tarball/v{}'.format(package_name, version), author='Sauce Labs', author_email='dev@saucelabs.com', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'Topic :: Utilities', 'License :: OSI Approved :: Apache Software License', 'Programming Language :: Python :: 2.7', ], keywords=['ios', 'app', 'signature', 'codesign', 'sign', 'resign'], packages=find_packages(), install_requires=[ 'biplist==1.0.3', 'construct==2.5.2', 'memoizer==0.0.1', 'pyOpenSSL==18.0.0', 'pyasn1==0.4.8', 'six>=1.10.0' ], package_data={ package: ['apple_credentials/applecerts.pem', 'code_resources_template.xml', 'version.json'], }, scripts=['bin/isign', 'bin/multisign', 'bin/isign_export_creds.sh', 'bin/isign_guess_mobileprovision.sh'] ) ================================================ FILE: tests/NotAnApp.txt ================================================ This file isn't actually an app. ================================================ FILE: tests/NotAnAppDir/README.md ================================================ This is a directory which isign shouldn't accept as being an AppArchive ================================================ FILE: tests/README.md ================================================ To test isign, we need to actually sign some apps. This directory contains the tests and the test data AND the source files to generate that test data. This directory includes a lot of stuff. It could probably be better organized. Test data: to check that the library can detect non-apps: * NotAnApp.ipa * NotAnApp.txt Test data - compiled apps: * Test.app * Test.app.zip * Test.ipa * TestSimulator.app.zip * TestWithFrameworks.ipa Expected data from apps: * Test.app.codesig.construct.txt A short program to generate that expected data: * generate_codesig_construct_txt.py A test file, to see what happens when "helper" apps go wrong: * bad_openssl Self-signed/fake credentials, to make these tests even work * credentials Source to build the test apps: * isignTestApp * isignTestAppWithFrameworks Helper for tests: * `monitor_temp_file.py` * `helpers.py` Actual test code: * `isign_base_test.py` * `test_*.py:` ================================================ FILE: tests/Test.app/PkgInfo ================================================ APPL???? ================================================ FILE: tests/Test.app/_CodeSignature/CodeResources ================================================ files Assets.car JTRsnG3T7BKKYvgn8rcVKnVf67c= Base.lproj/LaunchScreen.storyboardc/01J-lp-oVM-view-Ze5-6b-2t3.nib hash eMYxMmbROeqxPMenCgfCsYRPQKs= optional Base.lproj/LaunchScreen.storyboardc/Info.plist hash n2t8gsDpfE6XkhG31p7IQJRxTxU= optional Base.lproj/LaunchScreen.storyboardc/UIViewController-01J-lp-oVM.nib hash esMacQh55MAAFCgvM1T6IE5te+s= optional Base.lproj/Main.storyboardc/8rJ-Kc-sve-view-QS5-Rx-YEW.nib hash eIL8TQj/c89wIYSRBONbQO58A0k= optional Base.lproj/Main.storyboardc/9pv-A4-QxB-view-tsR-hK-woN.nib hash uMIVkX919bfq77m9Y4eVapG/uAI= optional Base.lproj/Main.storyboardc/Info.plist hash /BgmwrRK8ElRpzfemriLZvm3U/Y= optional Base.lproj/Main.storyboardc/UITabBarController-49e-Tb-3d3.nib hash 3Z4RvYT6WblSIhmHjkV4OCT0478= optional Frameworks/libswiftContacts.dylib XAUF5AOf/FKJ0FOaBryoXydYPkg= Frameworks/libswiftCore.dylib oPSCk2x86K18iCXpKgzk+xPXrUM= Frameworks/libswiftCoreGraphics.dylib lFIRixuLPuPvyFEA/4UmWEoWsT4= Frameworks/libswiftCoreImage.dylib 8w2X9ce86i4QEh2pxcwWJOw/hmY= Frameworks/libswiftDarwin.dylib dkfj0DypEBEArQE2yWzHKZXxeHM= Frameworks/libswiftDispatch.dylib jyXJbh2S33qT0cSQuNl8KYPMzBE= Frameworks/libswiftFoundation.dylib k5QXs7u4hyTXE+5NlHlz9KmMigg= Frameworks/libswiftObjectiveC.dylib Z1hjxXaY8/0BWfiaYAtBLah9NdQ= Frameworks/libswiftUIKit.dylib +mEoScPMQf+x9JhE1vClNhvkZIc= Info.plist yBg+lr48gYGDv7irgz8Co91E2c8= PkgInfo n57qDP4tZfLD1rCS43W0B4LQjzE= embedded.mobileprovision VHqTakGK9srUKBbgpbbbYG6u4bk= files2 Assets.car JTRsnG3T7BKKYvgn8rcVKnVf67c= Base.lproj/LaunchScreen.storyboardc/01J-lp-oVM-view-Ze5-6b-2t3.nib hash eMYxMmbROeqxPMenCgfCsYRPQKs= optional Base.lproj/LaunchScreen.storyboardc/Info.plist hash n2t8gsDpfE6XkhG31p7IQJRxTxU= optional Base.lproj/LaunchScreen.storyboardc/UIViewController-01J-lp-oVM.nib hash esMacQh55MAAFCgvM1T6IE5te+s= optional Base.lproj/Main.storyboardc/8rJ-Kc-sve-view-QS5-Rx-YEW.nib hash eIL8TQj/c89wIYSRBONbQO58A0k= optional Base.lproj/Main.storyboardc/9pv-A4-QxB-view-tsR-hK-woN.nib hash uMIVkX919bfq77m9Y4eVapG/uAI= optional Base.lproj/Main.storyboardc/Info.plist hash /BgmwrRK8ElRpzfemriLZvm3U/Y= optional Base.lproj/Main.storyboardc/UITabBarController-49e-Tb-3d3.nib hash 3Z4RvYT6WblSIhmHjkV4OCT0478= optional Frameworks/libswiftContacts.dylib XAUF5AOf/FKJ0FOaBryoXydYPkg= Frameworks/libswiftCore.dylib oPSCk2x86K18iCXpKgzk+xPXrUM= Frameworks/libswiftCoreGraphics.dylib lFIRixuLPuPvyFEA/4UmWEoWsT4= Frameworks/libswiftCoreImage.dylib 8w2X9ce86i4QEh2pxcwWJOw/hmY= Frameworks/libswiftDarwin.dylib dkfj0DypEBEArQE2yWzHKZXxeHM= Frameworks/libswiftDispatch.dylib jyXJbh2S33qT0cSQuNl8KYPMzBE= Frameworks/libswiftFoundation.dylib k5QXs7u4hyTXE+5NlHlz9KmMigg= Frameworks/libswiftObjectiveC.dylib Z1hjxXaY8/0BWfiaYAtBLah9NdQ= Frameworks/libswiftUIKit.dylib +mEoScPMQf+x9JhE1vClNhvkZIc= embedded.mobileprovision VHqTakGK9srUKBbgpbbbYG6u4bk= rules ^ ^.*\.lproj/ optional weight 1000 ^.*\.lproj/locversion.plist$ omit weight 1100 ^version.plist$ rules2 .*\.dSYM($|/) weight 11 ^ weight 20 ^(.*/)?\.DS_Store$ omit weight 2000 ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/ nested weight 10 ^.* ^.*\.lproj/ optional weight 1000 ^.*\.lproj/locversion.plist$ omit weight 1100 ^Info\.plist$ omit weight 20 ^PkgInfo$ omit weight 20 ^[^/]+$ nested weight 10 ^embedded\.provisionprofile$ weight 20 ^version\.plist$ weight 20 ================================================ FILE: tests/Test.app/build.sh ================================================ #!/bin/bash # ./build.sh /path/to/isign # builds various archival formats of the isign test app # and puts them in the test directory of isign. You have to specify # isign's source directory on the command line project=isignTestApp isign_dir=$1 isign_test_dir=$isign_dir/tests warn() { echo "$@" 1>&2; } build_app() { local platform=$1; local build_dir=build/Release-$(echo $platform | tr -d "0-9."); xcodebuild -project $project.xcodeproj -sdk $platform; } copy_app() { local platform=$1; local target=$2; local build_dir=$(build_app $platform) cp -r $build_dir/$project.app $target; } copy_app_zip() { local platform=$1; local target=$2; local build_dir=$(build_app $platform) pushd $build_dir; rm -f app.zip; zip -r app.zip $project.app; popd; mv $build_dir/app.zip $target; } build_ipa() { local platform=$1 local target=$2 xcodebuild clean -project $project.xcodeproj -configuration Release -sdk $platform; xcodebuild archive -project $project.xcodeproj -scheme $project -archivePath $project.xcarchive xcodebuild -exportArchive \ -archivePath $projectname.xcarchive \ -exportPath $projectname \ -exportFormat ipa \ -exportProvisioningProfile "Neil Kandalgaonkar" } copy_app iphoneos9.2 $isign_test_dir/Test.app; copy_app_zip iphoneos9.2 $isign_test_dir/Test.app.zip; # build_ipa iphoneos9.2 $isign_test_dir/Test.ipa; # build_app_zip iphonesimulator9.2 $isign_test_dir/TestSimulator.app.zip; ================================================ FILE: tests/Test.app/isignTestApp.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 88F49D571C21E0220007422C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88F49D561C21E0220007422C /* AppDelegate.swift */; }; 88F49D591C21E0220007422C /* FirstViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88F49D581C21E0220007422C /* FirstViewController.swift */; }; 88F49D5B1C21E0220007422C /* SecondViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88F49D5A1C21E0220007422C /* SecondViewController.swift */; }; 88F49D5E1C21E0220007422C /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 88F49D5C1C21E0220007422C /* Main.storyboard */; }; 88F49D601C21E0220007422C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 88F49D5F1C21E0220007422C /* Assets.xcassets */; }; 88F49D631C21E0220007422C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 88F49D611C21E0220007422C /* LaunchScreen.storyboard */; }; 88F49D6E1C21E0220007422C /* isignTestAppTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88F49D6D1C21E0220007422C /* isignTestAppTests.swift */; }; 88F49D791C21E0220007422C /* isignTestAppUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88F49D781C21E0220007422C /* isignTestAppUITests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 88F49D6A1C21E0220007422C /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 88F49D4B1C21E0220007422C /* Project object */; proxyType = 1; remoteGlobalIDString = 88F49D521C21E0220007422C; remoteInfo = isignTestApp; }; 88F49D751C21E0220007422C /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 88F49D4B1C21E0220007422C /* Project object */; proxyType = 1; remoteGlobalIDString = 88F49D521C21E0220007422C; remoteInfo = isignTestApp; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ 88F49D531C21E0220007422C /* isignTestApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = isignTestApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 88F49D561C21E0220007422C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 88F49D581C21E0220007422C /* FirstViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstViewController.swift; sourceTree = ""; }; 88F49D5A1C21E0220007422C /* SecondViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecondViewController.swift; sourceTree = ""; }; 88F49D5D1C21E0220007422C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 88F49D5F1C21E0220007422C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 88F49D621C21E0220007422C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 88F49D641C21E0220007422C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 88F49D691C21E0220007422C /* isignTestAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = isignTestAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 88F49D6D1C21E0220007422C /* isignTestAppTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = isignTestAppTests.swift; sourceTree = ""; }; 88F49D6F1C21E0220007422C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 88F49D741C21E0220007422C /* isignTestAppUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = isignTestAppUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 88F49D781C21E0220007422C /* isignTestAppUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = isignTestAppUITests.swift; sourceTree = ""; }; 88F49D7A1C21E0220007422C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 88F49D501C21E0220007422C /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 88F49D661C21E0220007422C /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 88F49D711C21E0220007422C /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 88F49D4A1C21E0220007422C = { isa = PBXGroup; children = ( 88F49D551C21E0220007422C /* isignTestApp */, 88F49D6C1C21E0220007422C /* isignTestAppTests */, 88F49D771C21E0220007422C /* isignTestAppUITests */, 88F49D541C21E0220007422C /* Products */, ); sourceTree = ""; }; 88F49D541C21E0220007422C /* Products */ = { isa = PBXGroup; children = ( 88F49D531C21E0220007422C /* isignTestApp.app */, 88F49D691C21E0220007422C /* isignTestAppTests.xctest */, 88F49D741C21E0220007422C /* isignTestAppUITests.xctest */, ); name = Products; sourceTree = ""; }; 88F49D551C21E0220007422C /* isignTestApp */ = { isa = PBXGroup; children = ( 88F49D561C21E0220007422C /* AppDelegate.swift */, 88F49D581C21E0220007422C /* FirstViewController.swift */, 88F49D5A1C21E0220007422C /* SecondViewController.swift */, 88F49D5C1C21E0220007422C /* Main.storyboard */, 88F49D5F1C21E0220007422C /* Assets.xcassets */, 88F49D611C21E0220007422C /* LaunchScreen.storyboard */, 88F49D641C21E0220007422C /* Info.plist */, ); path = isignTestApp; sourceTree = ""; }; 88F49D6C1C21E0220007422C /* isignTestAppTests */ = { isa = PBXGroup; children = ( 88F49D6D1C21E0220007422C /* isignTestAppTests.swift */, 88F49D6F1C21E0220007422C /* Info.plist */, ); path = isignTestAppTests; sourceTree = ""; }; 88F49D771C21E0220007422C /* isignTestAppUITests */ = { isa = PBXGroup; children = ( 88F49D781C21E0220007422C /* isignTestAppUITests.swift */, 88F49D7A1C21E0220007422C /* Info.plist */, ); path = isignTestAppUITests; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 88F49D521C21E0220007422C /* isignTestApp */ = { isa = PBXNativeTarget; buildConfigurationList = 88F49D7D1C21E0220007422C /* Build configuration list for PBXNativeTarget "isignTestApp" */; buildPhases = ( 88F49D4F1C21E0220007422C /* Sources */, 88F49D501C21E0220007422C /* Frameworks */, 88F49D511C21E0220007422C /* Resources */, ); buildRules = ( ); dependencies = ( ); name = isignTestApp; productName = isignTestApp; productReference = 88F49D531C21E0220007422C /* isignTestApp.app */; productType = "com.apple.product-type.application"; }; 88F49D681C21E0220007422C /* isignTestAppTests */ = { isa = PBXNativeTarget; buildConfigurationList = 88F49D801C21E0220007422C /* Build configuration list for PBXNativeTarget "isignTestAppTests" */; buildPhases = ( 88F49D651C21E0220007422C /* Sources */, 88F49D661C21E0220007422C /* Frameworks */, 88F49D671C21E0220007422C /* Resources */, ); buildRules = ( ); dependencies = ( 88F49D6B1C21E0220007422C /* PBXTargetDependency */, ); name = isignTestAppTests; productName = isignTestAppTests; productReference = 88F49D691C21E0220007422C /* isignTestAppTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; 88F49D731C21E0220007422C /* isignTestAppUITests */ = { isa = PBXNativeTarget; buildConfigurationList = 88F49D831C21E0220007422C /* Build configuration list for PBXNativeTarget "isignTestAppUITests" */; buildPhases = ( 88F49D701C21E0220007422C /* Sources */, 88F49D711C21E0220007422C /* Frameworks */, 88F49D721C21E0220007422C /* Resources */, ); buildRules = ( ); dependencies = ( 88F49D761C21E0220007422C /* PBXTargetDependency */, ); name = isignTestAppUITests; productName = isignTestAppUITests; productReference = 88F49D741C21E0220007422C /* isignTestAppUITests.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 88F49D4B1C21E0220007422C /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0720; LastUpgradeCheck = 0720; ORGANIZATIONNAME = "Sauce Labs"; TargetAttributes = { 88F49D521C21E0220007422C = { CreatedOnToolsVersion = 7.2; }; 88F49D681C21E0220007422C = { CreatedOnToolsVersion = 7.2; TestTargetID = 88F49D521C21E0220007422C; }; 88F49D731C21E0220007422C = { CreatedOnToolsVersion = 7.2; TestTargetID = 88F49D521C21E0220007422C; }; }; }; buildConfigurationList = 88F49D4E1C21E0220007422C /* Build configuration list for PBXProject "isignTestApp" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 88F49D4A1C21E0220007422C; productRefGroup = 88F49D541C21E0220007422C /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 88F49D521C21E0220007422C /* isignTestApp */, 88F49D681C21E0220007422C /* isignTestAppTests */, 88F49D731C21E0220007422C /* isignTestAppUITests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 88F49D511C21E0220007422C /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 88F49D631C21E0220007422C /* LaunchScreen.storyboard in Resources */, 88F49D601C21E0220007422C /* Assets.xcassets in Resources */, 88F49D5E1C21E0220007422C /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 88F49D671C21E0220007422C /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 88F49D721C21E0220007422C /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 88F49D4F1C21E0220007422C /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 88F49D5B1C21E0220007422C /* SecondViewController.swift in Sources */, 88F49D571C21E0220007422C /* AppDelegate.swift in Sources */, 88F49D591C21E0220007422C /* FirstViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 88F49D651C21E0220007422C /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 88F49D6E1C21E0220007422C /* isignTestAppTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 88F49D701C21E0220007422C /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 88F49D791C21E0220007422C /* isignTestAppUITests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 88F49D6B1C21E0220007422C /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 88F49D521C21E0220007422C /* isignTestApp */; targetProxy = 88F49D6A1C21E0220007422C /* PBXContainerItemProxy */; }; 88F49D761C21E0220007422C /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 88F49D521C21E0220007422C /* isignTestApp */; targetProxy = 88F49D751C21E0220007422C /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 88F49D5C1C21E0220007422C /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 88F49D5D1C21E0220007422C /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 88F49D611C21E0220007422C /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 88F49D621C21E0220007422C /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 88F49D7B1C21E0220007422C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.2; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 88F49D7C1C21E0220007422C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.2; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; }; name = Release; }; 88F49D7E1C21E0220007422C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = isignTestApp/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.saucelabs.isignTestApp; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 88F49D7F1C21E0220007422C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = isignTestApp/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.saucelabs.isignTestApp; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; 88F49D811C21E0220007422C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; INFOPLIST_FILE = isignTestAppTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.saucelabs.isignTestAppTests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/isignTestApp.app/isignTestApp"; }; name = Debug; }; 88F49D821C21E0220007422C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; INFOPLIST_FILE = isignTestAppTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.saucelabs.isignTestAppTests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/isignTestApp.app/isignTestApp"; }; name = Release; }; 88F49D841C21E0220007422C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { INFOPLIST_FILE = isignTestAppUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.saucelabs.isignTestAppUITests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_TARGET_NAME = isignTestApp; USES_XCTRUNNER = YES; }; name = Debug; }; 88F49D851C21E0220007422C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { INFOPLIST_FILE = isignTestAppUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.saucelabs.isignTestAppUITests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_TARGET_NAME = isignTestApp; USES_XCTRUNNER = YES; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 88F49D4E1C21E0220007422C /* Build configuration list for PBXProject "isignTestApp" */ = { isa = XCConfigurationList; buildConfigurations = ( 88F49D7B1C21E0220007422C /* Debug */, 88F49D7C1C21E0220007422C /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 88F49D7D1C21E0220007422C /* Build configuration list for PBXNativeTarget "isignTestApp" */ = { isa = XCConfigurationList; buildConfigurations = ( 88F49D7E1C21E0220007422C /* Debug */, 88F49D7F1C21E0220007422C /* Release */, ); defaultConfigurationIsVisible = 0; }; 88F49D801C21E0220007422C /* Build configuration list for PBXNativeTarget "isignTestAppTests" */ = { isa = XCConfigurationList; buildConfigurations = ( 88F49D811C21E0220007422C /* Debug */, 88F49D821C21E0220007422C /* Release */, ); defaultConfigurationIsVisible = 0; }; 88F49D831C21E0220007422C /* Build configuration list for PBXNativeTarget "isignTestAppUITests" */ = { isa = XCConfigurationList; buildConfigurations = ( 88F49D841C21E0220007422C /* Debug */, 88F49D851C21E0220007422C /* Release */, ); defaultConfigurationIsVisible = 0; }; /* End XCConfigurationList section */ }; rootObject = 88F49D4B1C21E0220007422C /* Project object */; } ================================================ FILE: tests/Test.app/isignTestApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: tests/Test.app/isignTestApp.xcodeproj/xcuserdata/neilk.xcuserdatad/xcschemes/isignTestApp.xcscheme ================================================ ================================================ FILE: tests/Test.app/isignTestApp.xcodeproj/xcuserdata/neilk.xcuserdatad/xcschemes/xcschememanagement.plist ================================================ SchemeUserState isignTestApp.xcscheme orderHint 0 SuppressBuildableAutocreation 88F49D521C21E0220007422C primary 88F49D681C21E0220007422C primary 88F49D731C21E0220007422C primary ================================================ FILE: tests/Test.app/isignTestAppTests/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 ================================================ FILE: tests/Test.app/isignTestAppTests/isignTestAppTests.swift ================================================ // // isignTestAppTests.swift // isignTestAppTests // // Created by Neil Kandalgaonkar on 12/16/15. // Copyright © 2015 Sauce Labs. All rights reserved. // import XCTest @testable import isignTestApp class isignTestAppTests: XCTestCase { override func setUp() { super.setUp() // Put setup code here. This method is called before the invocation of each test method in the class. } override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. super.tearDown() } func testExample() { // This is an example of a functional test case. // Use XCTAssert and related functions to verify your tests produce the correct results. } func testPerformanceExample() { // This is an example of a performance test case. self.measureBlock { // Put the code you want to measure the time of here. } } } ================================================ FILE: tests/Test.app/isignTestAppUITests/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 ================================================ FILE: tests/Test.app/isignTestAppUITests/isignTestAppUITests.swift ================================================ // // isignTestAppUITests.swift // isignTestAppUITests // // Created by Neil Kandalgaonkar on 12/16/15. // Copyright © 2015 Sauce Labs. All rights reserved. // import XCTest class isignTestAppUITests: XCTestCase { override func setUp() { super.setUp() // Put setup code here. This method is called before the invocation of each test method in the class. // In UI tests it is usually best to stop immediately when a failure occurs. continueAfterFailure = false // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. XCUIApplication().launch() // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. } override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. super.tearDown() } func testExample() { // Use recording to get started writing UI tests. // Use XCTAssert and related functions to verify your tests produce the correct results. } } ================================================ FILE: tests/Test.app.codesig.construct.txt ================================================ Container: cmd = 'LC_CODE_SIGNATURE' cmdsize = 16 data = Container: dataoff = 71920 datasize = 10304 blob = Container: magic = 'CSMAGIC_EMBEDDED_SIGNATURE' length = 5671 data = Container: sb_start = 88312 count = 4 BlobIndex = [ Container: type = 0 offset = 44 blob = Container: magic = 'CSMAGIC_CODEDIRECTORY' length = 550 data = Container: cd_start = 88356 version = 131584 flags = 0 hashOffset = 190 identOffset = 52 nSpecialSlots = 5 nCodeSlots = 18 codeLimit = 71920 hashSize = 20 hashType = 1 spare1 = 0 pageSize = 12 spare2 = 0 ident = 'com.saucelabs.isignTestApp' scatterOffset = 0 teamIDOffset = 79 teamID = 'L37S4Z6BE9' hashes = [ ':\x02\xd0\x07\x8az\xfa<.\xf7\x1ed8\x7f\xa4\xf1\xc1s\xeda' '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' 'TE\x05\xaa\x81<\xbc,\xb6\xd3\xf8\x05\xebM\xe1|\x1a\xb5O\x00' "\xc1,\xf7\x8b34\xa8\xe5\xfa\x92\xd69\x9d\xe2\x89Z'\xafo\x9b" '\xc8\x18>\x96\xbe<\x81\x81\x83\xbf\xb8\xab\x83?\x02\xa3\xddD\xd9\xcf' '\x0c\x02\x83\xf9\xfd\xa6\xb1\x9fC\x0b\xa3\x00\x135\xd8{\xb06\xeb\x92' '\x1c\xea\xf7=\xf4\x0eS\x1d\xf3\xbf\xb2kO\xb7\xcd\x95\xfb{\xff\x1d' '\x1c\xea\xf7=\xf4\x0eS\x1d\xf3\xbf\xb2kO\xb7\xcd\x95\xfb{\xff\x1d' '\x1c\xea\xf7=\xf4\x0eS\x1d\xf3\xbf\xb2kO\xb7\xcd\x95\xfb{\xff\x1d' '\x1c\xea\xf7=\xf4\x0eS\x1d\xf3\xbf\xb2kO\xb7\xcd\x95\xfb{\xff\x1d' '[\xd6\x8e\xe2\xfc\xed\x83\xbf5\x9b\x1fa{F`\x92\xba\x0b\xbfe' 'w\x99\xa6\x1cp\xc6, Container: type = 2 offset = 594 blob = Container: magic = 'CSMAGIC_REQUIREMENTS' length = 196 data = Container: sb_start = 88906 count = 1 BlobIndex = [ Container: type = 'kSecDesignatedRequirementType' offset = 20 blob = Container: magic = 'CSMAGIC_REQUIREMENT' length = 176 data = Container: kind = 1 expr = Container: op = 'opAnd' data = [ Container: op = 'opIdent' data = Container: length = 26 data = 'com.saucelabs.isignTestApp' Container: op = 'opAnd' data = [ Container: op = 'opAppleGenericAnchor' data = None Container: op = 'opAnd' data = [ Container: op = 'opCertField' data = [ 'leafCert' Container: length = 10 data = 'subject.CN' Container: matchOp = 'matchEqual' Data = Container: length = 49 data = 'iPhone Developer: Neil Kandalgaonkar (PJ5C3PEW8Z)' ] Container: op = 'opCertGeneric' data = [ 1 Container: length = 10 data = '*\x86H\x86\xf7cd\x06\x02\x01' Container: matchOp = 'matchExists' Data = None ] ] ] ] bytes = LazyContainer: ] bytes = LazyContainer: Container: type = 5 offset = 790 blob = Container: magic = 'CSMAGIC_ENTITLEMENT' length = 511 data = Container: data = {'application-identifier': 'L37S4Z6BE9.com.saucelabs.isignTestApp', 'com.apple.developer.team-identifier': 'L37S4Z6BE9', 'keychain-access-groups': ['L37S4Z6BE9.com.saucelabs.isignTestApp'], 'get-task-allow': True} bytes = LazyContainer: Container: type = 65536 offset = 1301 blob = Container: magic = 'CSMAGIC_BLOBWRAPPER' length = 4370 data = Container: data = LazyContainer: bytes = LazyContainer: ] bytes = LazyContainer: bytes = LazyContainer: ================================================ FILE: tests/Test_unsigned_fat.app/PkgInfo ================================================ APPL???? ================================================ FILE: tests/Test_unsigned_fat.app/build.sh ================================================ #!/bin/bash # ./build.sh /path/to/isign # builds various archival formats of the isign test app # and puts them in the test directory of isign. You have to specify # isign's source directory on the command line project=isignTestApp isign_dir=$1 isign_test_dir=$isign_dir/tests warn() { echo "$@" 1>&2; } build_app() { local platform=$1; local build_dir=build/Release-$(echo $platform | tr -d "0-9."); xcodebuild -project $project.xcodeproj -sdk $platform; } copy_app() { local platform=$1; local target=$2; local build_dir=$(build_app $platform) cp -r $build_dir/$project.app $target; } copy_app_zip() { local platform=$1; local target=$2; local build_dir=$(build_app $platform) pushd $build_dir; rm -f app.zip; zip -r app.zip $project.app; popd; mv $build_dir/app.zip $target; } build_ipa() { local platform=$1 local target=$2 xcodebuild clean -project $project.xcodeproj -configuration Release -sdk $platform; xcodebuild archive -project $project.xcodeproj -scheme $project -archivePath $project.xcarchive xcodebuild -exportArchive \ -archivePath $projectname.xcarchive \ -exportPath $projectname \ -exportFormat ipa \ -exportProvisioningProfile "Neil Kandalgaonkar" } copy_app iphoneos9.2 $isign_test_dir/Test.app; copy_app_zip iphoneos9.2 $isign_test_dir/Test.app.zip; # build_ipa iphoneos9.2 $isign_test_dir/Test.ipa; # build_app_zip iphonesimulator9.2 $isign_test_dir/TestSimulator.app.zip; ================================================ FILE: tests/Test_unsigned_fat.app/isignTestApp.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 88F49D571C21E0220007422C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88F49D561C21E0220007422C /* AppDelegate.swift */; }; 88F49D591C21E0220007422C /* FirstViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88F49D581C21E0220007422C /* FirstViewController.swift */; }; 88F49D5B1C21E0220007422C /* SecondViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88F49D5A1C21E0220007422C /* SecondViewController.swift */; }; 88F49D5E1C21E0220007422C /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 88F49D5C1C21E0220007422C /* Main.storyboard */; }; 88F49D601C21E0220007422C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 88F49D5F1C21E0220007422C /* Assets.xcassets */; }; 88F49D631C21E0220007422C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 88F49D611C21E0220007422C /* LaunchScreen.storyboard */; }; 88F49D6E1C21E0220007422C /* isignTestAppTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88F49D6D1C21E0220007422C /* isignTestAppTests.swift */; }; 88F49D791C21E0220007422C /* isignTestAppUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88F49D781C21E0220007422C /* isignTestAppUITests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 88F49D6A1C21E0220007422C /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 88F49D4B1C21E0220007422C /* Project object */; proxyType = 1; remoteGlobalIDString = 88F49D521C21E0220007422C; remoteInfo = isignTestApp; }; 88F49D751C21E0220007422C /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 88F49D4B1C21E0220007422C /* Project object */; proxyType = 1; remoteGlobalIDString = 88F49D521C21E0220007422C; remoteInfo = isignTestApp; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ 88F49D531C21E0220007422C /* isignTestApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = isignTestApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 88F49D561C21E0220007422C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 88F49D581C21E0220007422C /* FirstViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstViewController.swift; sourceTree = ""; }; 88F49D5A1C21E0220007422C /* SecondViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecondViewController.swift; sourceTree = ""; }; 88F49D5D1C21E0220007422C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 88F49D5F1C21E0220007422C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 88F49D621C21E0220007422C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 88F49D641C21E0220007422C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 88F49D691C21E0220007422C /* isignTestAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = isignTestAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 88F49D6D1C21E0220007422C /* isignTestAppTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = isignTestAppTests.swift; sourceTree = ""; }; 88F49D6F1C21E0220007422C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 88F49D741C21E0220007422C /* isignTestAppUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = isignTestAppUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 88F49D781C21E0220007422C /* isignTestAppUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = isignTestAppUITests.swift; sourceTree = ""; }; 88F49D7A1C21E0220007422C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 88F49D501C21E0220007422C /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 88F49D661C21E0220007422C /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 88F49D711C21E0220007422C /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 88F49D4A1C21E0220007422C = { isa = PBXGroup; children = ( 88F49D551C21E0220007422C /* isignTestApp */, 88F49D6C1C21E0220007422C /* isignTestAppTests */, 88F49D771C21E0220007422C /* isignTestAppUITests */, 88F49D541C21E0220007422C /* Products */, ); sourceTree = ""; }; 88F49D541C21E0220007422C /* Products */ = { isa = PBXGroup; children = ( 88F49D531C21E0220007422C /* isignTestApp.app */, 88F49D691C21E0220007422C /* isignTestAppTests.xctest */, 88F49D741C21E0220007422C /* isignTestAppUITests.xctest */, ); name = Products; sourceTree = ""; }; 88F49D551C21E0220007422C /* isignTestApp */ = { isa = PBXGroup; children = ( 88F49D561C21E0220007422C /* AppDelegate.swift */, 88F49D581C21E0220007422C /* FirstViewController.swift */, 88F49D5A1C21E0220007422C /* SecondViewController.swift */, 88F49D5C1C21E0220007422C /* Main.storyboard */, 88F49D5F1C21E0220007422C /* Assets.xcassets */, 88F49D611C21E0220007422C /* LaunchScreen.storyboard */, 88F49D641C21E0220007422C /* Info.plist */, ); path = isignTestApp; sourceTree = ""; }; 88F49D6C1C21E0220007422C /* isignTestAppTests */ = { isa = PBXGroup; children = ( 88F49D6D1C21E0220007422C /* isignTestAppTests.swift */, 88F49D6F1C21E0220007422C /* Info.plist */, ); path = isignTestAppTests; sourceTree = ""; }; 88F49D771C21E0220007422C /* isignTestAppUITests */ = { isa = PBXGroup; children = ( 88F49D781C21E0220007422C /* isignTestAppUITests.swift */, 88F49D7A1C21E0220007422C /* Info.plist */, ); path = isignTestAppUITests; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 88F49D521C21E0220007422C /* isignTestApp */ = { isa = PBXNativeTarget; buildConfigurationList = 88F49D7D1C21E0220007422C /* Build configuration list for PBXNativeTarget "isignTestApp" */; buildPhases = ( 88F49D4F1C21E0220007422C /* Sources */, 88F49D501C21E0220007422C /* Frameworks */, 88F49D511C21E0220007422C /* Resources */, ); buildRules = ( ); dependencies = ( ); name = isignTestApp; productName = isignTestApp; productReference = 88F49D531C21E0220007422C /* isignTestApp.app */; productType = "com.apple.product-type.application"; }; 88F49D681C21E0220007422C /* isignTestAppTests */ = { isa = PBXNativeTarget; buildConfigurationList = 88F49D801C21E0220007422C /* Build configuration list for PBXNativeTarget "isignTestAppTests" */; buildPhases = ( 88F49D651C21E0220007422C /* Sources */, 88F49D661C21E0220007422C /* Frameworks */, 88F49D671C21E0220007422C /* Resources */, ); buildRules = ( ); dependencies = ( 88F49D6B1C21E0220007422C /* PBXTargetDependency */, ); name = isignTestAppTests; productName = isignTestAppTests; productReference = 88F49D691C21E0220007422C /* isignTestAppTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; 88F49D731C21E0220007422C /* isignTestAppUITests */ = { isa = PBXNativeTarget; buildConfigurationList = 88F49D831C21E0220007422C /* Build configuration list for PBXNativeTarget "isignTestAppUITests" */; buildPhases = ( 88F49D701C21E0220007422C /* Sources */, 88F49D711C21E0220007422C /* Frameworks */, 88F49D721C21E0220007422C /* Resources */, ); buildRules = ( ); dependencies = ( 88F49D761C21E0220007422C /* PBXTargetDependency */, ); name = isignTestAppUITests; productName = isignTestAppUITests; productReference = 88F49D741C21E0220007422C /* isignTestAppUITests.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 88F49D4B1C21E0220007422C /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0720; LastUpgradeCheck = 0720; ORGANIZATIONNAME = "Sauce Labs"; TargetAttributes = { 88F49D521C21E0220007422C = { CreatedOnToolsVersion = 7.2; }; 88F49D681C21E0220007422C = { CreatedOnToolsVersion = 7.2; TestTargetID = 88F49D521C21E0220007422C; }; 88F49D731C21E0220007422C = { CreatedOnToolsVersion = 7.2; TestTargetID = 88F49D521C21E0220007422C; }; }; }; buildConfigurationList = 88F49D4E1C21E0220007422C /* Build configuration list for PBXProject "isignTestApp" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 88F49D4A1C21E0220007422C; productRefGroup = 88F49D541C21E0220007422C /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 88F49D521C21E0220007422C /* isignTestApp */, 88F49D681C21E0220007422C /* isignTestAppTests */, 88F49D731C21E0220007422C /* isignTestAppUITests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 88F49D511C21E0220007422C /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 88F49D631C21E0220007422C /* LaunchScreen.storyboard in Resources */, 88F49D601C21E0220007422C /* Assets.xcassets in Resources */, 88F49D5E1C21E0220007422C /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 88F49D671C21E0220007422C /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 88F49D721C21E0220007422C /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 88F49D4F1C21E0220007422C /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 88F49D5B1C21E0220007422C /* SecondViewController.swift in Sources */, 88F49D571C21E0220007422C /* AppDelegate.swift in Sources */, 88F49D591C21E0220007422C /* FirstViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 88F49D651C21E0220007422C /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 88F49D6E1C21E0220007422C /* isignTestAppTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 88F49D701C21E0220007422C /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 88F49D791C21E0220007422C /* isignTestAppUITests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 88F49D6B1C21E0220007422C /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 88F49D521C21E0220007422C /* isignTestApp */; targetProxy = 88F49D6A1C21E0220007422C /* PBXContainerItemProxy */; }; 88F49D761C21E0220007422C /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 88F49D521C21E0220007422C /* isignTestApp */; targetProxy = 88F49D751C21E0220007422C /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 88F49D5C1C21E0220007422C /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 88F49D5D1C21E0220007422C /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 88F49D611C21E0220007422C /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 88F49D621C21E0220007422C /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 88F49D7B1C21E0220007422C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.2; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 88F49D7C1C21E0220007422C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.2; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; }; name = Release; }; 88F49D7E1C21E0220007422C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = isignTestApp/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.saucelabs.isignTestApp; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 88F49D7F1C21E0220007422C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = isignTestApp/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.saucelabs.isignTestApp; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; 88F49D811C21E0220007422C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; INFOPLIST_FILE = isignTestAppTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.saucelabs.isignTestAppTests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/isignTestApp.app/isignTestApp"; }; name = Debug; }; 88F49D821C21E0220007422C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; INFOPLIST_FILE = isignTestAppTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.saucelabs.isignTestAppTests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/isignTestApp.app/isignTestApp"; }; name = Release; }; 88F49D841C21E0220007422C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { INFOPLIST_FILE = isignTestAppUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.saucelabs.isignTestAppUITests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_TARGET_NAME = isignTestApp; USES_XCTRUNNER = YES; }; name = Debug; }; 88F49D851C21E0220007422C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { INFOPLIST_FILE = isignTestAppUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.saucelabs.isignTestAppUITests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_TARGET_NAME = isignTestApp; USES_XCTRUNNER = YES; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 88F49D4E1C21E0220007422C /* Build configuration list for PBXProject "isignTestApp" */ = { isa = XCConfigurationList; buildConfigurations = ( 88F49D7B1C21E0220007422C /* Debug */, 88F49D7C1C21E0220007422C /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 88F49D7D1C21E0220007422C /* Build configuration list for PBXNativeTarget "isignTestApp" */ = { isa = XCConfigurationList; buildConfigurations = ( 88F49D7E1C21E0220007422C /* Debug */, 88F49D7F1C21E0220007422C /* Release */, ); defaultConfigurationIsVisible = 0; }; 88F49D801C21E0220007422C /* Build configuration list for PBXNativeTarget "isignTestAppTests" */ = { isa = XCConfigurationList; buildConfigurations = ( 88F49D811C21E0220007422C /* Debug */, 88F49D821C21E0220007422C /* Release */, ); defaultConfigurationIsVisible = 0; }; 88F49D831C21E0220007422C /* Build configuration list for PBXNativeTarget "isignTestAppUITests" */ = { isa = XCConfigurationList; buildConfigurations = ( 88F49D841C21E0220007422C /* Debug */, 88F49D851C21E0220007422C /* Release */, ); defaultConfigurationIsVisible = 0; }; /* End XCConfigurationList section */ }; rootObject = 88F49D4B1C21E0220007422C /* Project object */; } ================================================ FILE: tests/Test_unsigned_fat.app/isignTestApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: tests/Test_unsigned_fat.app/isignTestApp.xcodeproj/xcuserdata/neilk.xcuserdatad/xcschemes/isignTestApp.xcscheme ================================================ ================================================ FILE: tests/Test_unsigned_fat.app/isignTestApp.xcodeproj/xcuserdata/neilk.xcuserdatad/xcschemes/xcschememanagement.plist ================================================ SchemeUserState isignTestApp.xcscheme orderHint 0 SuppressBuildableAutocreation 88F49D521C21E0220007422C primary 88F49D681C21E0220007422C primary 88F49D731C21E0220007422C primary ================================================ FILE: tests/Test_unsigned_fat.app/isignTestAppTests/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 ================================================ FILE: tests/Test_unsigned_fat.app/isignTestAppTests/isignTestAppTests.swift ================================================ // // isignTestAppTests.swift // isignTestAppTests // // Created by Neil Kandalgaonkar on 12/16/15. // Copyright © 2015 Sauce Labs. All rights reserved. // import XCTest @testable import isignTestApp class isignTestAppTests: XCTestCase { override func setUp() { super.setUp() // Put setup code here. This method is called before the invocation of each test method in the class. } override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. super.tearDown() } func testExample() { // This is an example of a functional test case. // Use XCTAssert and related functions to verify your tests produce the correct results. } func testPerformanceExample() { // This is an example of a performance test case. self.measureBlock { // Put the code you want to measure the time of here. } } } ================================================ FILE: tests/Test_unsigned_fat.app/isignTestAppUITests/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 ================================================ FILE: tests/Test_unsigned_fat.app/isignTestAppUITests/isignTestAppUITests.swift ================================================ // // isignTestAppUITests.swift // isignTestAppUITests // // Created by Neil Kandalgaonkar on 12/16/15. // Copyright © 2015 Sauce Labs. All rights reserved. // import XCTest class isignTestAppUITests: XCTestCase { override func setUp() { super.setUp() // Put setup code here. This method is called before the invocation of each test method in the class. // In UI tests it is usually best to stop immediately when a failure occurs. continueAfterFailure = false // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. XCUIApplication().launch() // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. } override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. super.tearDown() } func testExample() { // Use recording to get started writing UI tests. // Use XCTAssert and related functions to verify your tests produce the correct results. } } ================================================ FILE: tests/Test_unsigned_thin.app/PkgInfo ================================================ APPL???? ================================================ FILE: tests/Test_unsigned_thin.app/build.sh ================================================ #!/bin/bash # ./build.sh /path/to/isign # builds various archival formats of the isign test app # and puts them in the test directory of isign. You have to specify # isign's source directory on the command line project=isignTestApp isign_dir=$1 isign_test_dir=$isign_dir/tests warn() { echo "$@" 1>&2; } build_app() { local platform=$1; local build_dir=build/Release-$(echo $platform | tr -d "0-9."); xcodebuild -project $project.xcodeproj -sdk $platform; } copy_app() { local platform=$1; local target=$2; local build_dir=$(build_app $platform) cp -r $build_dir/$project.app $target; } copy_app_zip() { local platform=$1; local target=$2; local build_dir=$(build_app $platform) pushd $build_dir; rm -f app.zip; zip -r app.zip $project.app; popd; mv $build_dir/app.zip $target; } build_ipa() { local platform=$1 local target=$2 xcodebuild clean -project $project.xcodeproj -configuration Release -sdk $platform; xcodebuild archive -project $project.xcodeproj -scheme $project -archivePath $project.xcarchive xcodebuild -exportArchive \ -archivePath $projectname.xcarchive \ -exportPath $projectname \ -exportFormat ipa \ -exportProvisioningProfile "Neil Kandalgaonkar" } copy_app iphoneos9.2 $isign_test_dir/Test.app; copy_app_zip iphoneos9.2 $isign_test_dir/Test.app.zip; # build_ipa iphoneos9.2 $isign_test_dir/Test.ipa; # build_app_zip iphonesimulator9.2 $isign_test_dir/TestSimulator.app.zip; ================================================ FILE: tests/Test_unsigned_thin.app/isignTestApp.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 88F49D571C21E0220007422C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88F49D561C21E0220007422C /* AppDelegate.swift */; }; 88F49D591C21E0220007422C /* FirstViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88F49D581C21E0220007422C /* FirstViewController.swift */; }; 88F49D5B1C21E0220007422C /* SecondViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88F49D5A1C21E0220007422C /* SecondViewController.swift */; }; 88F49D5E1C21E0220007422C /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 88F49D5C1C21E0220007422C /* Main.storyboard */; }; 88F49D601C21E0220007422C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 88F49D5F1C21E0220007422C /* Assets.xcassets */; }; 88F49D631C21E0220007422C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 88F49D611C21E0220007422C /* LaunchScreen.storyboard */; }; 88F49D6E1C21E0220007422C /* isignTestAppTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88F49D6D1C21E0220007422C /* isignTestAppTests.swift */; }; 88F49D791C21E0220007422C /* isignTestAppUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88F49D781C21E0220007422C /* isignTestAppUITests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 88F49D6A1C21E0220007422C /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 88F49D4B1C21E0220007422C /* Project object */; proxyType = 1; remoteGlobalIDString = 88F49D521C21E0220007422C; remoteInfo = isignTestApp; }; 88F49D751C21E0220007422C /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 88F49D4B1C21E0220007422C /* Project object */; proxyType = 1; remoteGlobalIDString = 88F49D521C21E0220007422C; remoteInfo = isignTestApp; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ 88F49D531C21E0220007422C /* isignTestApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = isignTestApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 88F49D561C21E0220007422C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 88F49D581C21E0220007422C /* FirstViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstViewController.swift; sourceTree = ""; }; 88F49D5A1C21E0220007422C /* SecondViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecondViewController.swift; sourceTree = ""; }; 88F49D5D1C21E0220007422C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 88F49D5F1C21E0220007422C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 88F49D621C21E0220007422C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 88F49D641C21E0220007422C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 88F49D691C21E0220007422C /* isignTestAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = isignTestAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 88F49D6D1C21E0220007422C /* isignTestAppTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = isignTestAppTests.swift; sourceTree = ""; }; 88F49D6F1C21E0220007422C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 88F49D741C21E0220007422C /* isignTestAppUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = isignTestAppUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 88F49D781C21E0220007422C /* isignTestAppUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = isignTestAppUITests.swift; sourceTree = ""; }; 88F49D7A1C21E0220007422C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 88F49D501C21E0220007422C /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 88F49D661C21E0220007422C /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 88F49D711C21E0220007422C /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 88F49D4A1C21E0220007422C = { isa = PBXGroup; children = ( 88F49D551C21E0220007422C /* isignTestApp */, 88F49D6C1C21E0220007422C /* isignTestAppTests */, 88F49D771C21E0220007422C /* isignTestAppUITests */, 88F49D541C21E0220007422C /* Products */, ); sourceTree = ""; }; 88F49D541C21E0220007422C /* Products */ = { isa = PBXGroup; children = ( 88F49D531C21E0220007422C /* isignTestApp.app */, 88F49D691C21E0220007422C /* isignTestAppTests.xctest */, 88F49D741C21E0220007422C /* isignTestAppUITests.xctest */, ); name = Products; sourceTree = ""; }; 88F49D551C21E0220007422C /* isignTestApp */ = { isa = PBXGroup; children = ( 88F49D561C21E0220007422C /* AppDelegate.swift */, 88F49D581C21E0220007422C /* FirstViewController.swift */, 88F49D5A1C21E0220007422C /* SecondViewController.swift */, 88F49D5C1C21E0220007422C /* Main.storyboard */, 88F49D5F1C21E0220007422C /* Assets.xcassets */, 88F49D611C21E0220007422C /* LaunchScreen.storyboard */, 88F49D641C21E0220007422C /* Info.plist */, ); path = isignTestApp; sourceTree = ""; }; 88F49D6C1C21E0220007422C /* isignTestAppTests */ = { isa = PBXGroup; children = ( 88F49D6D1C21E0220007422C /* isignTestAppTests.swift */, 88F49D6F1C21E0220007422C /* Info.plist */, ); path = isignTestAppTests; sourceTree = ""; }; 88F49D771C21E0220007422C /* isignTestAppUITests */ = { isa = PBXGroup; children = ( 88F49D781C21E0220007422C /* isignTestAppUITests.swift */, 88F49D7A1C21E0220007422C /* Info.plist */, ); path = isignTestAppUITests; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 88F49D521C21E0220007422C /* isignTestApp */ = { isa = PBXNativeTarget; buildConfigurationList = 88F49D7D1C21E0220007422C /* Build configuration list for PBXNativeTarget "isignTestApp" */; buildPhases = ( 88F49D4F1C21E0220007422C /* Sources */, 88F49D501C21E0220007422C /* Frameworks */, 88F49D511C21E0220007422C /* Resources */, ); buildRules = ( ); dependencies = ( ); name = isignTestApp; productName = isignTestApp; productReference = 88F49D531C21E0220007422C /* isignTestApp.app */; productType = "com.apple.product-type.application"; }; 88F49D681C21E0220007422C /* isignTestAppTests */ = { isa = PBXNativeTarget; buildConfigurationList = 88F49D801C21E0220007422C /* Build configuration list for PBXNativeTarget "isignTestAppTests" */; buildPhases = ( 88F49D651C21E0220007422C /* Sources */, 88F49D661C21E0220007422C /* Frameworks */, 88F49D671C21E0220007422C /* Resources */, ); buildRules = ( ); dependencies = ( 88F49D6B1C21E0220007422C /* PBXTargetDependency */, ); name = isignTestAppTests; productName = isignTestAppTests; productReference = 88F49D691C21E0220007422C /* isignTestAppTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; 88F49D731C21E0220007422C /* isignTestAppUITests */ = { isa = PBXNativeTarget; buildConfigurationList = 88F49D831C21E0220007422C /* Build configuration list for PBXNativeTarget "isignTestAppUITests" */; buildPhases = ( 88F49D701C21E0220007422C /* Sources */, 88F49D711C21E0220007422C /* Frameworks */, 88F49D721C21E0220007422C /* Resources */, ); buildRules = ( ); dependencies = ( 88F49D761C21E0220007422C /* PBXTargetDependency */, ); name = isignTestAppUITests; productName = isignTestAppUITests; productReference = 88F49D741C21E0220007422C /* isignTestAppUITests.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 88F49D4B1C21E0220007422C /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0720; LastUpgradeCheck = 0720; ORGANIZATIONNAME = "Sauce Labs"; TargetAttributes = { 88F49D521C21E0220007422C = { CreatedOnToolsVersion = 7.2; }; 88F49D681C21E0220007422C = { CreatedOnToolsVersion = 7.2; TestTargetID = 88F49D521C21E0220007422C; }; 88F49D731C21E0220007422C = { CreatedOnToolsVersion = 7.2; TestTargetID = 88F49D521C21E0220007422C; }; }; }; buildConfigurationList = 88F49D4E1C21E0220007422C /* Build configuration list for PBXProject "isignTestApp" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 88F49D4A1C21E0220007422C; productRefGroup = 88F49D541C21E0220007422C /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 88F49D521C21E0220007422C /* isignTestApp */, 88F49D681C21E0220007422C /* isignTestAppTests */, 88F49D731C21E0220007422C /* isignTestAppUITests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 88F49D511C21E0220007422C /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 88F49D631C21E0220007422C /* LaunchScreen.storyboard in Resources */, 88F49D601C21E0220007422C /* Assets.xcassets in Resources */, 88F49D5E1C21E0220007422C /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 88F49D671C21E0220007422C /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 88F49D721C21E0220007422C /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 88F49D4F1C21E0220007422C /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 88F49D5B1C21E0220007422C /* SecondViewController.swift in Sources */, 88F49D571C21E0220007422C /* AppDelegate.swift in Sources */, 88F49D591C21E0220007422C /* FirstViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 88F49D651C21E0220007422C /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 88F49D6E1C21E0220007422C /* isignTestAppTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 88F49D701C21E0220007422C /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 88F49D791C21E0220007422C /* isignTestAppUITests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 88F49D6B1C21E0220007422C /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 88F49D521C21E0220007422C /* isignTestApp */; targetProxy = 88F49D6A1C21E0220007422C /* PBXContainerItemProxy */; }; 88F49D761C21E0220007422C /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 88F49D521C21E0220007422C /* isignTestApp */; targetProxy = 88F49D751C21E0220007422C /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 88F49D5C1C21E0220007422C /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 88F49D5D1C21E0220007422C /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 88F49D611C21E0220007422C /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 88F49D621C21E0220007422C /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 88F49D7B1C21E0220007422C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.2; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 88F49D7C1C21E0220007422C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.2; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; }; name = Release; }; 88F49D7E1C21E0220007422C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = isignTestApp/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.saucelabs.isignTestApp; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 88F49D7F1C21E0220007422C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = isignTestApp/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.saucelabs.isignTestApp; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; 88F49D811C21E0220007422C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; INFOPLIST_FILE = isignTestAppTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.saucelabs.isignTestAppTests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/isignTestApp.app/isignTestApp"; }; name = Debug; }; 88F49D821C21E0220007422C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; INFOPLIST_FILE = isignTestAppTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.saucelabs.isignTestAppTests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/isignTestApp.app/isignTestApp"; }; name = Release; }; 88F49D841C21E0220007422C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { INFOPLIST_FILE = isignTestAppUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.saucelabs.isignTestAppUITests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_TARGET_NAME = isignTestApp; USES_XCTRUNNER = YES; }; name = Debug; }; 88F49D851C21E0220007422C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { INFOPLIST_FILE = isignTestAppUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.saucelabs.isignTestAppUITests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_TARGET_NAME = isignTestApp; USES_XCTRUNNER = YES; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 88F49D4E1C21E0220007422C /* Build configuration list for PBXProject "isignTestApp" */ = { isa = XCConfigurationList; buildConfigurations = ( 88F49D7B1C21E0220007422C /* Debug */, 88F49D7C1C21E0220007422C /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 88F49D7D1C21E0220007422C /* Build configuration list for PBXNativeTarget "isignTestApp" */ = { isa = XCConfigurationList; buildConfigurations = ( 88F49D7E1C21E0220007422C /* Debug */, 88F49D7F1C21E0220007422C /* Release */, ); defaultConfigurationIsVisible = 0; }; 88F49D801C21E0220007422C /* Build configuration list for PBXNativeTarget "isignTestAppTests" */ = { isa = XCConfigurationList; buildConfigurations = ( 88F49D811C21E0220007422C /* Debug */, 88F49D821C21E0220007422C /* Release */, ); defaultConfigurationIsVisible = 0; }; 88F49D831C21E0220007422C /* Build configuration list for PBXNativeTarget "isignTestAppUITests" */ = { isa = XCConfigurationList; buildConfigurations = ( 88F49D841C21E0220007422C /* Debug */, 88F49D851C21E0220007422C /* Release */, ); defaultConfigurationIsVisible = 0; }; /* End XCConfigurationList section */ }; rootObject = 88F49D4B1C21E0220007422C /* Project object */; } ================================================ FILE: tests/Test_unsigned_thin.app/isignTestApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: tests/Test_unsigned_thin.app/isignTestApp.xcodeproj/xcuserdata/neilk.xcuserdatad/xcschemes/isignTestApp.xcscheme ================================================ ================================================ FILE: tests/Test_unsigned_thin.app/isignTestApp.xcodeproj/xcuserdata/neilk.xcuserdatad/xcschemes/xcschememanagement.plist ================================================ SchemeUserState isignTestApp.xcscheme orderHint 0 SuppressBuildableAutocreation 88F49D521C21E0220007422C primary 88F49D681C21E0220007422C primary 88F49D731C21E0220007422C primary ================================================ FILE: tests/Test_unsigned_thin.app/isignTestAppTests/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 ================================================ FILE: tests/Test_unsigned_thin.app/isignTestAppTests/isignTestAppTests.swift ================================================ // // isignTestAppTests.swift // isignTestAppTests // // Created by Neil Kandalgaonkar on 12/16/15. // Copyright © 2015 Sauce Labs. All rights reserved. // import XCTest @testable import isignTestApp class isignTestAppTests: XCTestCase { override func setUp() { super.setUp() // Put setup code here. This method is called before the invocation of each test method in the class. } override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. super.tearDown() } func testExample() { // This is an example of a functional test case. // Use XCTAssert and related functions to verify your tests produce the correct results. } func testPerformanceExample() { // This is an example of a performance test case. self.measureBlock { // Put the code you want to measure the time of here. } } } ================================================ FILE: tests/Test_unsigned_thin.app/isignTestAppUITests/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 ================================================ FILE: tests/Test_unsigned_thin.app/isignTestAppUITests/isignTestAppUITests.swift ================================================ // // isignTestAppUITests.swift // isignTestAppUITests // // Created by Neil Kandalgaonkar on 12/16/15. // Copyright © 2015 Sauce Labs. All rights reserved. // import XCTest class isignTestAppUITests: XCTestCase { override func setUp() { super.setUp() // Put setup code here. This method is called before the invocation of each test method in the class. // In UI tests it is usually best to stop immediately when a failure occurs. continueAfterFailure = false // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. XCUIApplication().launch() // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. } override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. super.tearDown() } func testExample() { // Use recording to get started writing UI tests. // Use XCTAssert and related functions to verify your tests produce the correct results. } } ================================================ FILE: tests/bad_openssl ================================================ #!/bin/bash # returns nothing; simulates what happens with bad versions of openssl ================================================ FILE: tests/credentials/README.rst ================================================ The test credentials in this directory cannot be used to sign apps for use on any iOS device. They are sufficiently similar to a real key, certificate and provisioning profile to make the tests pass. The key was generated solely for this library, and the certificate is self-signed. A working key would have to be certified by Apple. The provisioning profile was created with makeFakePprof.sh and test.mobileprovision.plist. The structure is based on a real provisioning profile, but all data has been swapped for meaningless noise. ================================================ FILE: tests/credentials/makeFakePprof.sh ================================================ #!/bin/bash openssl smime -sign -in test.mobileprovision.plist -outform der -out test.mobileprovision -signer test.cert.pem -inkey test.key.pem -nodetach ================================================ FILE: tests/credentials/test.cert.pem ================================================ Bag Attributes friendlyName: isign_tests localKeyID: 25 67 EB F6 B5 12 B7 2F E7 38 49 10 87 2C 92 7D 0A 9D C2 63 subject=/CN=isign_tests/O=isign tests/OU=ISIGNTESTS/ST=CA/C=US/L=San Francisco/emailAddress=dev@saucelabs.com issuer=/CN=isign_tests/O=isign tests/OU=ISIGNTESTS/ST=CA/C=US/L=San Francisco/emailAddress=dev@saucelabs.com -----BEGIN CERTIFICATE----- MIID0TCCArmgAwIBAgIBATANBgkqhkiG9w0BAQsFADCBlTEUMBIGA1UEAwwLaXNp Z25fdGVzdHMxFDASBgNVBAoMC2lzaWduIHRlc3RzMRMwEQYDVQQLDApJU0lHTlRF U1RTMQswCQYDVQQIDAJDQTELMAkGA1UEBhMCVVMxFjAUBgNVBAcMDVNhbiBGcmFu Y2lzY28xIDAeBgkqhkiG9w0BCQEWEWRldkBzYXVjZWxhYnMuY29tMB4XDTE1MTAy MjE5MTMwN1oXDTI1MTAxOTE5MTMwN1owgZUxFDASBgNVBAMMC2lzaWduX3Rlc3Rz MRQwEgYDVQQKDAtpc2lnbiB0ZXN0czETMBEGA1UECwwKSVNJR05URVNUUzELMAkG A1UECAwCQ0ExCzAJBgNVBAYTAlVTMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMSAw HgYJKoZIhvcNAQkBFhFkZXZAc2F1Y2VsYWJzLmNvbTCCASIwDQYJKoZIhvcNAQEB BQADggEPADCCAQoCggEBAPKEyqq6txSChIsoVv0tgAI4o8BDGqAUwqjUT9Ha1byK pUS/YVtHeaC9ozPNSndlAfFvUiwV+MD0rPznZojQnKtYy6RCIaGySox6FeNqFmL+ xBUTwQaV2IqZEKUJLSDLY4ExPf98LLdxrieyUWR+ldA0Vatne6aTxqeKjEwUKbBW kcSJrYM9I4Z/Z6AGomph+blXWOg0ly5QshxJzXDjn7i8MdW0ry3YpIHLDoSJieW/ rUNSBiG5b1xy3TNqSrUjyCK4qr0HJqPFaDfuzCKyUJVnYZHkt1+qZ1mmX2CWavtE 2+s7Uf/Tw10fMszXjXKIP7uxlTOjbfPfgE2Q3RUCSosCAwEAAaMqMCgwDgYDVR0P AQH/BAQDAgeAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMDMA0GCSqGSIb3DQEBCwUA A4IBAQChOZBtgbtJshb/hEm9Ue06TQEqFCdn7N4MoiuwNgq7oO4/xikFBdCE6dcR BaqdnsqSFOGvSfNPW1O/ha4nMP65UoKxsoFI40CpjTwRpHUH7g9OGK7a9nsLGDla h3BR80V2j2FmgJbjuSQaFSvI4f7rOSAJrOUFu54ofLK3FokjjurpjsaKS+6NDnzB PXwBsXBbvjvlyb/0ejLVRiYBYSLfmdjjYpBF+3HaDAzEDXCylZdOzvf11o2aOYHL MaEANSFZe09sV2JecwnxoWEWGb7+IgZKNq0hn1ckhumib1gmOkOp6JtyxviEB1yw E4pd+A1hdAwzsqKr3xxZmisaV8yH -----END CERTIFICATE----- ================================================ FILE: tests/credentials/test.key.pem ================================================ Bag Attributes friendlyName: isign_tests localKeyID: 25 67 EB F6 B5 12 B7 2F E7 38 49 10 87 2C 92 7D 0A 9D C2 63 Key Attributes: -----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEA8oTKqrq3FIKEiyhW/S2AAjijwEMaoBTCqNRP0drVvIqlRL9h W0d5oL2jM81Kd2UB8W9SLBX4wPSs/OdmiNCcq1jLpEIhobJKjHoV42oWYv7EFRPB BpXYipkQpQktIMtjgTE9/3wst3GuJ7JRZH6V0DRVq2d7ppPGp4qMTBQpsFaRxImt gz0jhn9noAaiamH5uVdY6DSXLlCyHEnNcOOfuLwx1bSvLdikgcsOhImJ5b+tQ1IG IblvXHLdM2pKtSPIIriqvQcmo8VoN+7MIrJQlWdhkeS3X6pnWaZfYJZq+0Tb6ztR /9PDXR8yzNeNcog/u7GVM6Nt89+ATZDdFQJKiwIDAQABAoIBAQDtyhx5qJgIqSzS 0Vvx5KImC2ksA9/gZFq1dW9KQbreokcIEGqiOIPegvK5wSmpxcVQ+Kjmhyif8YiI dU/JCFsFeww5Y4pcZFfKQ46grA2FRW33iKX0Egr/YrO6TjQMesB3FVRH5HZn7DQp wiMiWSgFvLrfVJkeLLlU33lOw1pZBbAhNaZK+DQ2nuLDg1mIbJ1B0VRvEo5zVS4r CZo8otr3MAkdTbjhqaoGDZuRnjzd72Zl/VCxlg55IJP45WV2hHWt7ofH0RjQ4KSA XIuh9KJWf9f6vVv2oZ+nY6P6EA2mzIBKzlfpcPNvUeh+gW4Ln2kbsxnIp2VxU2D+ UH+oVclpAoGBAP+Risu98rg1et1Sh5oHe2MIZ18n2M01qhqdJx5fY28+7GHVTdvn BMaooojpxYJ/aEY2M5oZfG6twCpLN5+YTFsycDsdL5ALoMqGLDdBbtNjmVzQUzuN g0oZvu6Xi9qN9GGF84falS+tchAOGQahw+mpWRtqnmNI60geo5V/Ng+lAoGBAPLt m/vwM/s09XnFozfX2tdpWUjPUnDRCFWQBSNpnqjeUi4B/evMxXhZIT+z2Vg+cRru A4xLRJ2ZQbEYsTPxejCoMJsKD48pFi1KDDAN0HQSKPwKgwHoBJjIGoxb4gV5Uz0T FiXMp8667vI2O6VlQZYfvx1K4qsbjRBLQReyE9pvAoGAMVx5BFURtkaODoVML5HW YRBUduqJU0lUK4PC9HjUCb6LhXHfceOy5nPXwL32KfhVuYnqeY8Gm5HvlbulaKvP w6WAS8qdTyMP0U4M6Dc5IpcQHf8WtF/mxb8nQ4n9tx7H3rYyjUDIo8bKSxON/dvs rQrKbSMwqFiDKRDR46QIWwkCgYBL4l1vfcE/H3Pu7gXmU89Qqt/xFpIyG5n24F46 iau+JHSXWpfvKLAmv/Da7VtfbWH3f5IsKPbuJAmZQJVaHHyG5oMIa0Qg/DypORBF kosjNukGkmIKHmgRMbDZV3prK1MNKQEqHZvtwAcUMky/hbnPI2uXwuS7foe6J0fW FxKMkQKBgBzo3nCUC9nu48abop7wOBUERZAGVTdHEHA97wIgpSkAagMBrkf0f+fN hx00UefaJQLvJNPmUQ8t0rmdQX0HkGVAPA2Eek8e2fjpvzjhyUNKb47VllBBm/H/ qoQZOsKh8YWy373DPydoi9MsFal4E1Pi56co1At6hf4TICWJBedd -----END RSA PRIVATE KEY----- ================================================ FILE: tests/credentials/test.mobileprovision.plist ================================================ AppIDName isign tests ApplicationIdentifierPrefix ISIGNTESTS CreationDate 2016-11-28T22:57:49Z Platform iOS DeveloperCertificates MIID0TCCArmgAwIBAgIBATANBgkqhkiG9w0BAQsFADCBlTEUMBIGA1UEAwwLaXNpZ25fdGVzdHMxFDASBgNVBAoMC2lzaWduIHRlc3RzMRMwEQYDVQQLDApJU0lHTlRFU1RTMQswCQYDVQQIDAJDQTELMAkGA1UEBhMCVVMxFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xIDAeBgkqhkiG9w0BCQEWEWRldkBzYXVjZWxhYnMuY29tMB4XDTE1MTAyMjE5MTMwN1oXDTI1MTAxOTE5MTMwN1owgZUxFDASBgNVBAMMC2lzaWduX3Rlc3RzMRQwEgYDVQQKDAtpc2lnbiB0ZXN0czETMBEGA1UECwwKSVNJR05URVNUUzELMAkGA1UECAwCQ0ExCzAJBgNVBAYTAlVTMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMSAwHgYJKoZIhvcNAQkBFhFkZXZAc2F1Y2VsYWJzLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPKEyqq6txSChIsoVv0tgAI4o8BDGqAUwqjUT9Ha1byKpUS/YVtHeaC9ozPNSndlAfFvUiwV+MD0rPznZojQnKtYy6RCIaGySox6FeNqFmL+xBUTwQaV2IqZEKUJLSDLY4ExPf98LLdxrieyUWR+ldA0Vatne6aTxqeKjEwUKbBWkcSJrYM9I4Z/Z6AGomph+blXWOg0ly5QshxJzXDjn7i8MdW0ry3YpIHLDoSJieW/rUNSBiG5b1xy3TNqSrUjyCK4qr0HJqPFaDfuzCKyUJVnYZHkt1+qZ1mmX2CWavtE2+s7Uf/Tw10fMszXjXKIP7uxlTOjbfPfgE2Q3RUCSosCAwEAAaMqMCgwDgYDVR0PAQH/BAQDAgeAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMDMA0GCSqGSIb3DQEBCwUAA4IBAQChOZBtgbtJshb/hEm9Ue06TQEqFCdn7N4MoiuwNgq7oO4/xikFBdCE6dcRBaqdnsqSFOGvSfNPW1O/ha4nMP65UoKxsoFI40CpjTwRpHUH7g9OGK7a9nsLGDlah3BR80V2j2FmgJbjuSQaFSvI4f7rOSAJrOUFu54ofLK3FokjjurpjsaKS+6NDnzBPXwBsXBbvjvlyb/0ejLVRiYBYSLfmdjjYpBF+3HaDAzEDXCylZdOzvf11o2aOYHLMaEANSFZe09sV2JecwnxoWEWGb7+IgZKNq0hn1ckhumib1gmOkOp6JtyxviEB1ywE4pd+A1hdAwzsqKr3xxZmisaV8yH Entitlements keychain-access-groups ISIGNTESTS.* get-task-allow application-identifier ISIGNTESTS.* com.apple.developer.team-identifier ISIGNTESTS ExpirationDate 2017-11-28T22:57:49Z Name RDC-Dev ProvisionedDevices e2cd3a02c1a3f69a5ff05bce3ea1b1092b30efe0 0a0fd3dcb248d3c001a586e8f638800e18c54efc TeamIdentifier ISIGNTESTS TeamName ISIGN OPEN SOURCE TimeToLive 365 UUID f4d8aa37-45a7-4304-960c-1291e1a5a0f3 Version 1 ================================================ FILE: tests/credentials_std_names/README.rst ================================================ These credentials exist to test the 'credentials directory' feature in isign. The credentials are copied from ../credentials and just have the correct names. ================================================ FILE: tests/credentials_std_names/certificate.pem ================================================ Bag Attributes friendlyName: isign_tests localKeyID: 25 67 EB F6 B5 12 B7 2F E7 38 49 10 87 2C 92 7D 0A 9D C2 63 subject=/CN=isign_tests/O=isign tests/OU=ISIGNTESTS/ST=CA/C=US/L=San Francisco/emailAddress=dev@saucelabs.com issuer=/CN=isign_tests/O=isign tests/OU=ISIGNTESTS/ST=CA/C=US/L=San Francisco/emailAddress=dev@saucelabs.com -----BEGIN CERTIFICATE----- MIID0TCCArmgAwIBAgIBATANBgkqhkiG9w0BAQsFADCBlTEUMBIGA1UEAwwLaXNp Z25fdGVzdHMxFDASBgNVBAoMC2lzaWduIHRlc3RzMRMwEQYDVQQLDApJU0lHTlRF U1RTMQswCQYDVQQIDAJDQTELMAkGA1UEBhMCVVMxFjAUBgNVBAcMDVNhbiBGcmFu Y2lzY28xIDAeBgkqhkiG9w0BCQEWEWRldkBzYXVjZWxhYnMuY29tMB4XDTE1MTAy MjE5MTMwN1oXDTI1MTAxOTE5MTMwN1owgZUxFDASBgNVBAMMC2lzaWduX3Rlc3Rz MRQwEgYDVQQKDAtpc2lnbiB0ZXN0czETMBEGA1UECwwKSVNJR05URVNUUzELMAkG A1UECAwCQ0ExCzAJBgNVBAYTAlVTMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMSAw HgYJKoZIhvcNAQkBFhFkZXZAc2F1Y2VsYWJzLmNvbTCCASIwDQYJKoZIhvcNAQEB BQADggEPADCCAQoCggEBAPKEyqq6txSChIsoVv0tgAI4o8BDGqAUwqjUT9Ha1byK pUS/YVtHeaC9ozPNSndlAfFvUiwV+MD0rPznZojQnKtYy6RCIaGySox6FeNqFmL+ xBUTwQaV2IqZEKUJLSDLY4ExPf98LLdxrieyUWR+ldA0Vatne6aTxqeKjEwUKbBW kcSJrYM9I4Z/Z6AGomph+blXWOg0ly5QshxJzXDjn7i8MdW0ry3YpIHLDoSJieW/ rUNSBiG5b1xy3TNqSrUjyCK4qr0HJqPFaDfuzCKyUJVnYZHkt1+qZ1mmX2CWavtE 2+s7Uf/Tw10fMszXjXKIP7uxlTOjbfPfgE2Q3RUCSosCAwEAAaMqMCgwDgYDVR0P AQH/BAQDAgeAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMDMA0GCSqGSIb3DQEBCwUA A4IBAQChOZBtgbtJshb/hEm9Ue06TQEqFCdn7N4MoiuwNgq7oO4/xikFBdCE6dcR BaqdnsqSFOGvSfNPW1O/ha4nMP65UoKxsoFI40CpjTwRpHUH7g9OGK7a9nsLGDla h3BR80V2j2FmgJbjuSQaFSvI4f7rOSAJrOUFu54ofLK3FokjjurpjsaKS+6NDnzB PXwBsXBbvjvlyb/0ejLVRiYBYSLfmdjjYpBF+3HaDAzEDXCylZdOzvf11o2aOYHL MaEANSFZe09sV2JecwnxoWEWGb7+IgZKNq0hn1ckhumib1gmOkOp6JtyxviEB1yw E4pd+A1hdAwzsqKr3xxZmisaV8yH -----END CERTIFICATE----- ================================================ FILE: tests/credentials_std_names_2/README.rst ================================================ These credentials exist to test the 'multisign' feature in isign The credentials are copied from ../credentials_std_names, and exist solely to have a separate path to point to them. ================================================ FILE: tests/credentials_std_names_2/certificate.pem ================================================ Bag Attributes friendlyName: isign_tests localKeyID: 25 67 EB F6 B5 12 B7 2F E7 38 49 10 87 2C 92 7D 0A 9D C2 63 subject=/CN=isign_tests/O=isign tests/OU=ISIGNTESTS/ST=CA/C=US/L=San Francisco/emailAddress=dev@saucelabs.com issuer=/CN=isign_tests/O=isign tests/OU=ISIGNTESTS/ST=CA/C=US/L=San Francisco/emailAddress=dev@saucelabs.com -----BEGIN CERTIFICATE----- MIID0TCCArmgAwIBAgIBATANBgkqhkiG9w0BAQsFADCBlTEUMBIGA1UEAwwLaXNp Z25fdGVzdHMxFDASBgNVBAoMC2lzaWduIHRlc3RzMRMwEQYDVQQLDApJU0lHTlRF U1RTMQswCQYDVQQIDAJDQTELMAkGA1UEBhMCVVMxFjAUBgNVBAcMDVNhbiBGcmFu Y2lzY28xIDAeBgkqhkiG9w0BCQEWEWRldkBzYXVjZWxhYnMuY29tMB4XDTE1MTAy MjE5MTMwN1oXDTI1MTAxOTE5MTMwN1owgZUxFDASBgNVBAMMC2lzaWduX3Rlc3Rz MRQwEgYDVQQKDAtpc2lnbiB0ZXN0czETMBEGA1UECwwKSVNJR05URVNUUzELMAkG A1UECAwCQ0ExCzAJBgNVBAYTAlVTMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMSAw HgYJKoZIhvcNAQkBFhFkZXZAc2F1Y2VsYWJzLmNvbTCCASIwDQYJKoZIhvcNAQEB BQADggEPADCCAQoCggEBAPKEyqq6txSChIsoVv0tgAI4o8BDGqAUwqjUT9Ha1byK pUS/YVtHeaC9ozPNSndlAfFvUiwV+MD0rPznZojQnKtYy6RCIaGySox6FeNqFmL+ xBUTwQaV2IqZEKUJLSDLY4ExPf98LLdxrieyUWR+ldA0Vatne6aTxqeKjEwUKbBW kcSJrYM9I4Z/Z6AGomph+blXWOg0ly5QshxJzXDjn7i8MdW0ry3YpIHLDoSJieW/ rUNSBiG5b1xy3TNqSrUjyCK4qr0HJqPFaDfuzCKyUJVnYZHkt1+qZ1mmX2CWavtE 2+s7Uf/Tw10fMszXjXKIP7uxlTOjbfPfgE2Q3RUCSosCAwEAAaMqMCgwDgYDVR0P AQH/BAQDAgeAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMDMA0GCSqGSIb3DQEBCwUA A4IBAQChOZBtgbtJshb/hEm9Ue06TQEqFCdn7N4MoiuwNgq7oO4/xikFBdCE6dcR BaqdnsqSFOGvSfNPW1O/ha4nMP65UoKxsoFI40CpjTwRpHUH7g9OGK7a9nsLGDla h3BR80V2j2FmgJbjuSQaFSvI4f7rOSAJrOUFu54ofLK3FokjjurpjsaKS+6NDnzB PXwBsXBbvjvlyb/0ejLVRiYBYSLfmdjjYpBF+3HaDAzEDXCylZdOzvf11o2aOYHL MaEANSFZe09sV2JecwnxoWEWGb7+IgZKNq0hn1ckhumib1gmOkOp6JtyxviEB1yw E4pd+A1hdAwzsqKr3xxZmisaV8yH -----END CERTIFICATE----- ================================================ FILE: tests/credentials_std_names_2/key.pem ================================================ Bag Attributes friendlyName: isign_tests localKeyID: 25 67 EB F6 B5 12 B7 2F E7 38 49 10 87 2C 92 7D 0A 9D C2 63 Key Attributes: -----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEA8oTKqrq3FIKEiyhW/S2AAjijwEMaoBTCqNRP0drVvIqlRL9h W0d5oL2jM81Kd2UB8W9SLBX4wPSs/OdmiNCcq1jLpEIhobJKjHoV42oWYv7EFRPB BpXYipkQpQktIMtjgTE9/3wst3GuJ7JRZH6V0DRVq2d7ppPGp4qMTBQpsFaRxImt gz0jhn9noAaiamH5uVdY6DSXLlCyHEnNcOOfuLwx1bSvLdikgcsOhImJ5b+tQ1IG IblvXHLdM2pKtSPIIriqvQcmo8VoN+7MIrJQlWdhkeS3X6pnWaZfYJZq+0Tb6ztR /9PDXR8yzNeNcog/u7GVM6Nt89+ATZDdFQJKiwIDAQABAoIBAQDtyhx5qJgIqSzS 0Vvx5KImC2ksA9/gZFq1dW9KQbreokcIEGqiOIPegvK5wSmpxcVQ+Kjmhyif8YiI dU/JCFsFeww5Y4pcZFfKQ46grA2FRW33iKX0Egr/YrO6TjQMesB3FVRH5HZn7DQp wiMiWSgFvLrfVJkeLLlU33lOw1pZBbAhNaZK+DQ2nuLDg1mIbJ1B0VRvEo5zVS4r CZo8otr3MAkdTbjhqaoGDZuRnjzd72Zl/VCxlg55IJP45WV2hHWt7ofH0RjQ4KSA XIuh9KJWf9f6vVv2oZ+nY6P6EA2mzIBKzlfpcPNvUeh+gW4Ln2kbsxnIp2VxU2D+ UH+oVclpAoGBAP+Risu98rg1et1Sh5oHe2MIZ18n2M01qhqdJx5fY28+7GHVTdvn BMaooojpxYJ/aEY2M5oZfG6twCpLN5+YTFsycDsdL5ALoMqGLDdBbtNjmVzQUzuN g0oZvu6Xi9qN9GGF84falS+tchAOGQahw+mpWRtqnmNI60geo5V/Ng+lAoGBAPLt m/vwM/s09XnFozfX2tdpWUjPUnDRCFWQBSNpnqjeUi4B/evMxXhZIT+z2Vg+cRru A4xLRJ2ZQbEYsTPxejCoMJsKD48pFi1KDDAN0HQSKPwKgwHoBJjIGoxb4gV5Uz0T FiXMp8667vI2O6VlQZYfvx1K4qsbjRBLQReyE9pvAoGAMVx5BFURtkaODoVML5HW YRBUduqJU0lUK4PC9HjUCb6LhXHfceOy5nPXwL32KfhVuYnqeY8Gm5HvlbulaKvP w6WAS8qdTyMP0U4M6Dc5IpcQHf8WtF/mxb8nQ4n9tx7H3rYyjUDIo8bKSxON/dvs rQrKbSMwqFiDKRDR46QIWwkCgYBL4l1vfcE/H3Pu7gXmU89Qqt/xFpIyG5n24F46 iau+JHSXWpfvKLAmv/Da7VtfbWH3f5IsKPbuJAmZQJVaHHyG5oMIa0Qg/DypORBF kosjNukGkmIKHmgRMbDZV3prK1MNKQEqHZvtwAcUMky/hbnPI2uXwuS7foe6J0fW FxKMkQKBgBzo3nCUC9nu48abop7wOBUERZAGVTdHEHA97wIgpSkAagMBrkf0f+fN hx00UefaJQLvJNPmUQ8t0rmdQX0HkGVAPA2Eek8e2fjpvzjhyUNKb47VllBBm/H/ qoQZOsKh8YWy373DPydoi9MsFal4E1Pi56co1At6hf4TICWJBedd -----END RSA PRIVATE KEY----- ================================================ FILE: tests/generate_codesig_construct_txt.py ================================================ # generate a string representation of a signature parse for an app - do this to generate # test files such as Test.app.codesig.construct.txt from isign_base_test import IsignBaseTest import isign.archive from isign.signable import Executable import logging FORMATTER = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') def log_to_stderr(level=logging.INFO): root = logging.getLogger() root.setLevel(level) handler = logging.StreamHandler() handler.setFormatter(FORMATTER) root.addHandler(handler) log_to_stderr(logging.DEBUG) log = logging.getLogger(__name__) log.debug("generating a signature parse for an app") app = isign.archive.AppArchive(IsignBaseTest.TEST_APP) executable = Executable(app.get_executable_path()) arch = executable.arches[0] codesig_str = str(arch['cmds']['LC_CODE_SIGNATURE']) print codesig_str ================================================ FILE: tests/isignTestApp/.gitignore ================================================ build ================================================ FILE: tests/isignTestApp/README.md ================================================ # isignTestApp Project to create a very simple test app for isign's test suite. Run `./build.sh` to create the necessary test app, app.zip, .ipa, and simulator app in the isign test directory (which is the containing directory). ## Caveats Currently, the TeamID specified is Neil Kandalgaonkar (neilk@neilk.net's) personal organization ID. For this to work, you would also need a provisioning profile installed in the right places on your system (the obvious thing is to use XCode). So... you pretty much need to be Neil to build this, unless you modify the `build.sh` script. For obvious reasons this is a problem going forward - we'll need to create some sort of Apple account which the community can use to sign the test apps. But on the assumption that we will only need to recreate the test apps very rarely, we're open sourcing it as it is, for now. ================================================ FILE: tests/isignTestApp/build.sh ================================================ #!/bin/bash # ./build.sh /path/to/isign # builds various archival formats of the isign test app # and puts them in the test directory of isign. You have to specify # isign's source directory on the command line # # Also you have to have the provisioning profile mentioned configured # in the app. # isign_test_dir=.. project=isignTestApp # this project just has one scheme configured, same name as project scheme=$project # This file must exist in this directory. export_options_plist=exportOptions.plist warn() { echo "$@" 1>&2; } platform_to_build_dir() { echo build/Release-$(echo $1 | tr -d "0-9."); } build_app() { local platform=$1; xcodebuild -project $project.xcodeproj -sdk $platform >&2; } copy_app() { local platform=$1; local target=$2; build_app $platform local build_dir=$(platform_to_build_dir $platform); rm -r $target cp -r $build_dir/$project.app $target; } copy_app_zip() { local platform=$1; local target=$2; build_app $platform local build_dir=$(platform_to_build_dir $platform); pushd $build_dir; rm -f app.zip; zip -r app.zip $project.app; popd; mv $build_dir/app.zip $target; } copy_ipa() { local platform=$1 local target=$2 archive_path=build/$project.xcarchive ipa_dir=build ipa_path=$ipa_dir/$project.ipa rm -f $ipa_path; xcodebuild clean -project $project.xcodeproj -configuration Release -sdk $platform; xcodebuild archive -project $project.xcodeproj -scheme $scheme -archivePath $archive_path; xcodebuild -exportArchive \ -archivePath $archive_path \ -exportPath $ipa_dir \ -exportOptionsPlist $export_options_plist cp $ipa_path $target; } if [[ -n $rvm_path ]]; then warn "========"; warn "" warn "ACHTUNG!! " warn "rvm users! switch to the system version of ruby with: "; warn " $ rvm use system"; warn "otherwise your path to some ruby development tools like ipatool may be wrong"; warn "" warn "========"; fi copy_app iphoneos9.2 $isign_test_dir/Test.app; copy_app_zip iphoneos9.2 $isign_test_dir/Test.app.zip; copy_ipa iphoneos9.2 $isign_test_dir/Test.ipa; copy_app_zip iphonesimulator9.2 $isign_test_dir/TestSimulator.app.zip; ================================================ FILE: tests/isignTestApp/exportOptions.plist ================================================ teamID L37S4Z6BE9 method development uploadSymbols ================================================ FILE: tests/isignTestApp/isignTestApp/AppDelegate.swift ================================================ // // AppDelegate.swift // isignTestApp // // Copyright © 2015 Sauce Labs. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { // Override point for customization after application launch. return true } func applicationWillResignActive(application: UIApplication) { // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. } func applicationDidEnterBackground(application: UIApplication) { // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. } func applicationWillEnterForeground(application: UIApplication) { // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. } func applicationDidBecomeActive(application: UIApplication) { // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. } func applicationWillTerminate(application: UIApplication) { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } } ================================================ FILE: tests/isignTestApp/isignTestApp/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "iphone", "size" : "29x29", "scale" : "2x" }, { "idiom" : "iphone", "size" : "29x29", "scale" : "3x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "2x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "3x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "2x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: tests/isignTestApp/isignTestApp/Assets.xcassets/first.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "first.pdf" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: tests/isignTestApp/isignTestApp/Assets.xcassets/second.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "second.pdf" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: tests/isignTestApp/isignTestApp/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: tests/isignTestApp/isignTestApp/Base.lproj/Main.storyboard ================================================ ================================================ FILE: tests/isignTestApp/isignTestApp/FirstViewController.swift ================================================ // // FirstViewController.swift // isignTestApp // // Copyright © 2015 Sauce Labs. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // import UIKit class FirstViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } } ================================================ FILE: tests/isignTestApp/isignTestApp/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities armv7 UIStatusBarTintParameters UINavigationBar Style UIBarStyleDefault Translucent UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: tests/isignTestApp/isignTestApp/SecondViewController.swift ================================================ // // SecondViewController.swift // isignTestApp // // Copyright © 2015 Sauce Labs. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // import UIKit class SecondViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } } ================================================ FILE: tests/isignTestApp/isignTestApp.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 88F49D571C21E0220007422C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88F49D561C21E0220007422C /* AppDelegate.swift */; }; 88F49D591C21E0220007422C /* FirstViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88F49D581C21E0220007422C /* FirstViewController.swift */; }; 88F49D5B1C21E0220007422C /* SecondViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88F49D5A1C21E0220007422C /* SecondViewController.swift */; }; 88F49D5E1C21E0220007422C /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 88F49D5C1C21E0220007422C /* Main.storyboard */; }; 88F49D601C21E0220007422C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 88F49D5F1C21E0220007422C /* Assets.xcassets */; }; 88F49D631C21E0220007422C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 88F49D611C21E0220007422C /* LaunchScreen.storyboard */; }; 88F49D6E1C21E0220007422C /* isignTestAppTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88F49D6D1C21E0220007422C /* isignTestAppTests.swift */; }; 88F49D791C21E0220007422C /* isignTestAppUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88F49D781C21E0220007422C /* isignTestAppUITests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 88F49D6A1C21E0220007422C /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 88F49D4B1C21E0220007422C /* Project object */; proxyType = 1; remoteGlobalIDString = 88F49D521C21E0220007422C; remoteInfo = isignTestApp; }; 88F49D751C21E0220007422C /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 88F49D4B1C21E0220007422C /* Project object */; proxyType = 1; remoteGlobalIDString = 88F49D521C21E0220007422C; remoteInfo = isignTestApp; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ 88F49D531C21E0220007422C /* isignTestApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = isignTestApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 88F49D561C21E0220007422C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 88F49D581C21E0220007422C /* FirstViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstViewController.swift; sourceTree = ""; }; 88F49D5A1C21E0220007422C /* SecondViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecondViewController.swift; sourceTree = ""; }; 88F49D5D1C21E0220007422C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 88F49D5F1C21E0220007422C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 88F49D621C21E0220007422C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 88F49D641C21E0220007422C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 88F49D691C21E0220007422C /* isignTestAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = isignTestAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 88F49D6D1C21E0220007422C /* isignTestAppTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = isignTestAppTests.swift; sourceTree = ""; }; 88F49D6F1C21E0220007422C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 88F49D741C21E0220007422C /* isignTestAppUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = isignTestAppUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 88F49D781C21E0220007422C /* isignTestAppUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = isignTestAppUITests.swift; sourceTree = ""; }; 88F49D7A1C21E0220007422C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 88F49D501C21E0220007422C /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 88F49D661C21E0220007422C /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 88F49D711C21E0220007422C /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 88F49D4A1C21E0220007422C = { isa = PBXGroup; children = ( 88F49D551C21E0220007422C /* isignTestApp */, 88F49D6C1C21E0220007422C /* isignTestAppTests */, 88F49D771C21E0220007422C /* isignTestAppUITests */, 88F49D541C21E0220007422C /* Products */, ); sourceTree = ""; }; 88F49D541C21E0220007422C /* Products */ = { isa = PBXGroup; children = ( 88F49D531C21E0220007422C /* isignTestApp.app */, 88F49D691C21E0220007422C /* isignTestAppTests.xctest */, 88F49D741C21E0220007422C /* isignTestAppUITests.xctest */, ); name = Products; sourceTree = ""; }; 88F49D551C21E0220007422C /* isignTestApp */ = { isa = PBXGroup; children = ( 88F49D561C21E0220007422C /* AppDelegate.swift */, 88F49D581C21E0220007422C /* FirstViewController.swift */, 88F49D5A1C21E0220007422C /* SecondViewController.swift */, 88F49D5C1C21E0220007422C /* Main.storyboard */, 88F49D5F1C21E0220007422C /* Assets.xcassets */, 88F49D611C21E0220007422C /* LaunchScreen.storyboard */, 88F49D641C21E0220007422C /* Info.plist */, ); path = isignTestApp; sourceTree = ""; }; 88F49D6C1C21E0220007422C /* isignTestAppTests */ = { isa = PBXGroup; children = ( 88F49D6D1C21E0220007422C /* isignTestAppTests.swift */, 88F49D6F1C21E0220007422C /* Info.plist */, ); path = isignTestAppTests; sourceTree = ""; }; 88F49D771C21E0220007422C /* isignTestAppUITests */ = { isa = PBXGroup; children = ( 88F49D781C21E0220007422C /* isignTestAppUITests.swift */, 88F49D7A1C21E0220007422C /* Info.plist */, ); path = isignTestAppUITests; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 88F49D521C21E0220007422C /* isignTestApp */ = { isa = PBXNativeTarget; buildConfigurationList = 88F49D7D1C21E0220007422C /* Build configuration list for PBXNativeTarget "isignTestApp" */; buildPhases = ( 88F49D4F1C21E0220007422C /* Sources */, 88F49D501C21E0220007422C /* Frameworks */, 88F49D511C21E0220007422C /* Resources */, ); buildRules = ( ); dependencies = ( ); name = isignTestApp; productName = isignTestApp; productReference = 88F49D531C21E0220007422C /* isignTestApp.app */; productType = "com.apple.product-type.application"; }; 88F49D681C21E0220007422C /* isignTestAppTests */ = { isa = PBXNativeTarget; buildConfigurationList = 88F49D801C21E0220007422C /* Build configuration list for PBXNativeTarget "isignTestAppTests" */; buildPhases = ( 88F49D651C21E0220007422C /* Sources */, 88F49D661C21E0220007422C /* Frameworks */, 88F49D671C21E0220007422C /* Resources */, ); buildRules = ( ); dependencies = ( 88F49D6B1C21E0220007422C /* PBXTargetDependency */, ); name = isignTestAppTests; productName = isignTestAppTests; productReference = 88F49D691C21E0220007422C /* isignTestAppTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; 88F49D731C21E0220007422C /* isignTestAppUITests */ = { isa = PBXNativeTarget; buildConfigurationList = 88F49D831C21E0220007422C /* Build configuration list for PBXNativeTarget "isignTestAppUITests" */; buildPhases = ( 88F49D701C21E0220007422C /* Sources */, 88F49D711C21E0220007422C /* Frameworks */, 88F49D721C21E0220007422C /* Resources */, ); buildRules = ( ); dependencies = ( 88F49D761C21E0220007422C /* PBXTargetDependency */, ); name = isignTestAppUITests; productName = isignTestAppUITests; productReference = 88F49D741C21E0220007422C /* isignTestAppUITests.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 88F49D4B1C21E0220007422C /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0720; LastUpgradeCheck = 0720; ORGANIZATIONNAME = "Sauce Labs"; TargetAttributes = { 88F49D521C21E0220007422C = { CreatedOnToolsVersion = 7.2; }; 88F49D681C21E0220007422C = { CreatedOnToolsVersion = 7.2; TestTargetID = 88F49D521C21E0220007422C; }; 88F49D731C21E0220007422C = { CreatedOnToolsVersion = 7.2; TestTargetID = 88F49D521C21E0220007422C; }; }; }; buildConfigurationList = 88F49D4E1C21E0220007422C /* Build configuration list for PBXProject "isignTestApp" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 88F49D4A1C21E0220007422C; productRefGroup = 88F49D541C21E0220007422C /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 88F49D521C21E0220007422C /* isignTestApp */, 88F49D681C21E0220007422C /* isignTestAppTests */, 88F49D731C21E0220007422C /* isignTestAppUITests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 88F49D511C21E0220007422C /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 88F49D631C21E0220007422C /* LaunchScreen.storyboard in Resources */, 88F49D601C21E0220007422C /* Assets.xcassets in Resources */, 88F49D5E1C21E0220007422C /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 88F49D671C21E0220007422C /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 88F49D721C21E0220007422C /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 88F49D4F1C21E0220007422C /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 88F49D5B1C21E0220007422C /* SecondViewController.swift in Sources */, 88F49D571C21E0220007422C /* AppDelegate.swift in Sources */, 88F49D591C21E0220007422C /* FirstViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 88F49D651C21E0220007422C /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 88F49D6E1C21E0220007422C /* isignTestAppTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 88F49D701C21E0220007422C /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 88F49D791C21E0220007422C /* isignTestAppUITests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 88F49D6B1C21E0220007422C /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 88F49D521C21E0220007422C /* isignTestApp */; targetProxy = 88F49D6A1C21E0220007422C /* PBXContainerItemProxy */; }; 88F49D761C21E0220007422C /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 88F49D521C21E0220007422C /* isignTestApp */; targetProxy = 88F49D751C21E0220007422C /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 88F49D5C1C21E0220007422C /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 88F49D5D1C21E0220007422C /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 88F49D611C21E0220007422C /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 88F49D621C21E0220007422C /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 88F49D7B1C21E0220007422C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.2; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 88F49D7C1C21E0220007422C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.2; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; }; name = Release; }; 88F49D7E1C21E0220007422C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = isignTestApp/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.saucelabs.isignTestApp; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 88F49D7F1C21E0220007422C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = isignTestApp/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.saucelabs.isignTestApp; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; 88F49D811C21E0220007422C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; INFOPLIST_FILE = isignTestAppTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.saucelabs.isignTestAppTests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/isignTestApp.app/isignTestApp"; }; name = Debug; }; 88F49D821C21E0220007422C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; INFOPLIST_FILE = isignTestAppTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.saucelabs.isignTestAppTests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/isignTestApp.app/isignTestApp"; }; name = Release; }; 88F49D841C21E0220007422C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { INFOPLIST_FILE = isignTestAppUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.saucelabs.isignTestAppUITests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_TARGET_NAME = isignTestApp; USES_XCTRUNNER = YES; }; name = Debug; }; 88F49D851C21E0220007422C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { INFOPLIST_FILE = isignTestAppUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.saucelabs.isignTestAppUITests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_TARGET_NAME = isignTestApp; USES_XCTRUNNER = YES; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 88F49D4E1C21E0220007422C /* Build configuration list for PBXProject "isignTestApp" */ = { isa = XCConfigurationList; buildConfigurations = ( 88F49D7B1C21E0220007422C /* Debug */, 88F49D7C1C21E0220007422C /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 88F49D7D1C21E0220007422C /* Build configuration list for PBXNativeTarget "isignTestApp" */ = { isa = XCConfigurationList; buildConfigurations = ( 88F49D7E1C21E0220007422C /* Debug */, 88F49D7F1C21E0220007422C /* Release */, ); defaultConfigurationIsVisible = 0; }; 88F49D801C21E0220007422C /* Build configuration list for PBXNativeTarget "isignTestAppTests" */ = { isa = XCConfigurationList; buildConfigurations = ( 88F49D811C21E0220007422C /* Debug */, 88F49D821C21E0220007422C /* Release */, ); defaultConfigurationIsVisible = 0; }; 88F49D831C21E0220007422C /* Build configuration list for PBXNativeTarget "isignTestAppUITests" */ = { isa = XCConfigurationList; buildConfigurations = ( 88F49D841C21E0220007422C /* Debug */, 88F49D851C21E0220007422C /* Release */, ); defaultConfigurationIsVisible = 0; }; /* End XCConfigurationList section */ }; rootObject = 88F49D4B1C21E0220007422C /* Project object */; } ================================================ FILE: tests/isignTestApp/isignTestApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: tests/isignTestApp/isignTestApp.xcodeproj/xcuserdata/neilk.xcuserdatad/xcschemes/isignTestApp.xcscheme ================================================ ================================================ FILE: tests/isignTestApp/isignTestApp.xcodeproj/xcuserdata/neilk.xcuserdatad/xcschemes/xcschememanagement.plist ================================================ SchemeUserState isignTestApp.xcscheme orderHint 0 SuppressBuildableAutocreation 88F49D521C21E0220007422C primary 88F49D681C21E0220007422C primary 88F49D731C21E0220007422C primary ================================================ FILE: tests/isignTestApp/isignTestAppTests/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 ================================================ FILE: tests/isignTestApp/isignTestAppTests/isignTestAppTests.swift ================================================ // // isignTestAppTests.swift // isignTestAppTests // // Copyright © 2015 Sauce Labs. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // import XCTest @testable import isignTestApp class isignTestAppTests: XCTestCase { override func setUp() { super.setUp() // Put setup code here. This method is called before the invocation of each test method in the class. } override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. super.tearDown() } func testExample() { // This is an example of a functional test case. // Use XCTAssert and related functions to verify your tests produce the correct results. } func testPerformanceExample() { // This is an example of a performance test case. self.measureBlock { // Put the code you want to measure the time of here. } } } ================================================ FILE: tests/isignTestApp/isignTestAppUITests/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 ================================================ FILE: tests/isignTestApp/isignTestAppUITests/isignTestAppUITests.swift ================================================ // // isignTestAppUITests.swift // isignTestAppUITests // // Copyright © 2015 Sauce Labs. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // import XCTest class isignTestAppUITests: XCTestCase { override func setUp() { super.setUp() // Put setup code here. This method is called before the invocation of each test method in the class. // In UI tests it is usually best to stop immediately when a failure occurs. continueAfterFailure = false // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. XCUIApplication().launch() // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. } override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. super.tearDown() } func testExample() { // Use recording to get started writing UI tests. // Use XCTAssert and related functions to verify your tests produce the correct results. } } ================================================ FILE: tests/isignTestAppWithFrameworks/.gitignore ================================================ build Pods ================================================ FILE: tests/isignTestAppWithFrameworks/Podfile ================================================ source 'https://github.com/CocoaPods/Specs.git' platform :ios, '8.0' use_frameworks! target 'isignTestApp' do pod 'FontAwesome.swift', '~> 0.7' end target 'isignTestAppTests' do end target 'isignTestAppUITests' do end ================================================ FILE: tests/isignTestAppWithFrameworks/README.md ================================================ # isignTestApp Project to create a very simple test app for isign's test suite. This is exactly like the isignTestApp, except that if it's working, it should also display a lightning bolt on the main screen. This app includes Github user @thii's [FontAwesome_swift Framework](https://github.com/thii/FontAwesome_swift), which crucially for our test, includes both a small resource (a font file) and a small amount of code. # Prerequisites This project uses frameworks from the [CocoaPods](http://cocoapods.org) system. To obtain the frameworks, install Cocoapods, then run `pod install` in this directory. # Building Run `./build.sh` to create the necessary test .ipa in the isign test directory (which is the containing directory). ## Caveats Currently, the TeamID specified is Neil Kandalgaonkar (neilk@neilk.net)'s' personal organization ID. For this to work, you would also need a provisioning profile installed in the right places on your system (the obvious thing is to use XCode). So... you pretty much need to be Neil to build this, unless you modify the `build.sh` script and the `exportOptions.plist` in this directory. For obvious reasons this is a problem going forward. Perhaps we'll need to create some sort of Apple account which the community can use to sign the test apps. But on the assumption that we will only need to recreate the test apps very rarely, we're open sourcing it as it is, for now. ================================================ FILE: tests/isignTestAppWithFrameworks/build.sh ================================================ #!/bin/bash # ./build.sh /path/to/isign # builds various archival formats of the isign test app # and puts them in the test directory of isign. You have to specify # isign's source directory on the command line # # Also you have to have the provisioning profile mentioned configured # in the app. # isign_test_dir=.. projectName=isignTestApp # workspace workspace=$projectName.xcworkspace # this projectName just has one scheme configured, same name as projectName scheme=$projectName # This file must exist in this directory. export_options_plist=exportOptions.plist warn() { echo "$@" 1>&2; } copy_ipa() { local platform=$1 local target=$2 archive_path=build/$projectName.xcarchive ipa_dir=build ipa_path=$ipa_dir/$projectName.ipa rm -f $ipa_path; xcodebuild clean -workspace $workspace \ -scheme $scheme \ -configuration Release \ -sdk $platform; xcodebuild archive -workspace $workspace \ -scheme $scheme \ -archivePath $archive_path; xcodebuild -exportArchive \ -archivePath $archive_path \ -exportPath $ipa_dir \ -exportOptionsPlist $export_options_plist; cp $ipa_path $target; } if [[ -n $rvm_path ]]; then warn "========"; warn "" warn "ACHTUNG!! " warn "rvm users! switch to the system version of ruby with: "; warn " $ rvm use system"; warn "otherwise your path to some ruby development tools like ipatool may be wrong"; warn "" warn "========"; fi copy_ipa iphoneos9.2 $isign_test_dir/TestWithFrameworks.ipa; ================================================ FILE: tests/isignTestAppWithFrameworks/exportOptions.plist ================================================ teamID L37S4Z6BE9 method development compileBitcode uploadSymbols ================================================ FILE: tests/isignTestAppWithFrameworks/isignTestApp/AppDelegate.swift ================================================ // // AppDelegate.swift // isignTestApp // // Copyright © 2015 Sauce Labs. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { // Override point for customization after application launch. return true } func applicationWillResignActive(application: UIApplication) { // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. } func applicationDidEnterBackground(application: UIApplication) { // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. } func applicationWillEnterForeground(application: UIApplication) { // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. } func applicationDidBecomeActive(application: UIApplication) { // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. } func applicationWillTerminate(application: UIApplication) { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } } ================================================ FILE: tests/isignTestAppWithFrameworks/isignTestApp/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "iphone", "size" : "29x29", "scale" : "2x" }, { "idiom" : "iphone", "size" : "29x29", "scale" : "3x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "2x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "3x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "2x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: tests/isignTestAppWithFrameworks/isignTestApp/Assets.xcassets/first.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "first.pdf" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: tests/isignTestAppWithFrameworks/isignTestApp/Assets.xcassets/second.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "second.pdf" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: tests/isignTestAppWithFrameworks/isignTestApp/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: tests/isignTestAppWithFrameworks/isignTestApp/Base.lproj/Main.storyboard ================================================ ================================================ FILE: tests/isignTestAppWithFrameworks/isignTestApp/FirstViewController.swift ================================================ // // FirstViewController.swift // isignTestApp // // Copyright © 2015 Sauce Labs. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // import UIKit import FontAwesome_swift class FirstViewController: UIViewController { @IBOutlet weak var boltLabel: UILabel! override func viewDidLoad() { super.viewDidLoad() boltLabel.font = UIFont.fontAwesomeOfSize(100); boltLabel.text = String.fontAwesomeIconWithName(FontAwesome.Bolt); boltLabel.textAlignment = NSTextAlignment.Center; } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } } ================================================ FILE: tests/isignTestAppWithFrameworks/isignTestApp/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities armv7 UIStatusBarTintParameters UINavigationBar Style UIBarStyleDefault Translucent UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: tests/isignTestAppWithFrameworks/isignTestApp/SecondViewController.swift ================================================ // // SecondViewController.swift // isignTestApp // // Copyright © 2015 Sauce Labs. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // import UIKit class SecondViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } } ================================================ FILE: tests/isignTestAppWithFrameworks/isignTestApp.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 83E2137C82FE1346AD0FE999 /* Pods_isignTestApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 482C83E341072C026C4E2D04 /* Pods_isignTestApp.framework */; }; 88F49D571C21E0220007422C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88F49D561C21E0220007422C /* AppDelegate.swift */; }; 88F49D591C21E0220007422C /* FirstViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88F49D581C21E0220007422C /* FirstViewController.swift */; }; 88F49D5B1C21E0220007422C /* SecondViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88F49D5A1C21E0220007422C /* SecondViewController.swift */; }; 88F49D5E1C21E0220007422C /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 88F49D5C1C21E0220007422C /* Main.storyboard */; }; 88F49D601C21E0220007422C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 88F49D5F1C21E0220007422C /* Assets.xcassets */; }; 88F49D631C21E0220007422C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 88F49D611C21E0220007422C /* LaunchScreen.storyboard */; }; 88F49D6E1C21E0220007422C /* isignTestAppTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88F49D6D1C21E0220007422C /* isignTestAppTests.swift */; }; 88F49D791C21E0220007422C /* isignTestAppUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88F49D781C21E0220007422C /* isignTestAppUITests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 88F49D6A1C21E0220007422C /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 88F49D4B1C21E0220007422C /* Project object */; proxyType = 1; remoteGlobalIDString = 88F49D521C21E0220007422C; remoteInfo = isignTestApp; }; 88F49D751C21E0220007422C /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 88F49D4B1C21E0220007422C /* Project object */; proxyType = 1; remoteGlobalIDString = 88F49D521C21E0220007422C; remoteInfo = isignTestApp; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ 1A03A6031C2E68A83409A36A /* Pods-isignTestApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-isignTestApp.release.xcconfig"; path = "Pods/Target Support Files/Pods-isignTestApp/Pods-isignTestApp.release.xcconfig"; sourceTree = ""; }; 482C83E341072C026C4E2D04 /* Pods_isignTestApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_isignTestApp.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 568EA0A65D28521957385887 /* Pods-isignTestApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-isignTestApp.debug.xcconfig"; path = "Pods/Target Support Files/Pods-isignTestApp/Pods-isignTestApp.debug.xcconfig"; sourceTree = ""; }; 88F49D531C21E0220007422C /* isignTestApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = isignTestApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 88F49D561C21E0220007422C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 88F49D581C21E0220007422C /* FirstViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstViewController.swift; sourceTree = ""; }; 88F49D5A1C21E0220007422C /* SecondViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecondViewController.swift; sourceTree = ""; }; 88F49D5D1C21E0220007422C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 88F49D5F1C21E0220007422C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 88F49D621C21E0220007422C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 88F49D641C21E0220007422C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 88F49D691C21E0220007422C /* isignTestAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = isignTestAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 88F49D6D1C21E0220007422C /* isignTestAppTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = isignTestAppTests.swift; sourceTree = ""; }; 88F49D6F1C21E0220007422C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 88F49D741C21E0220007422C /* isignTestAppUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = isignTestAppUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 88F49D781C21E0220007422C /* isignTestAppUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = isignTestAppUITests.swift; sourceTree = ""; }; 88F49D7A1C21E0220007422C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 88F49D501C21E0220007422C /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 83E2137C82FE1346AD0FE999 /* Pods_isignTestApp.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 88F49D661C21E0220007422C /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 88F49D711C21E0220007422C /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 8767BD99DB0DE852EFDCE52D /* Pods */ = { isa = PBXGroup; children = ( 568EA0A65D28521957385887 /* Pods-isignTestApp.debug.xcconfig */, 1A03A6031C2E68A83409A36A /* Pods-isignTestApp.release.xcconfig */, ); name = Pods; sourceTree = ""; }; 88F49D4A1C21E0220007422C = { isa = PBXGroup; children = ( 88F49D551C21E0220007422C /* isignTestApp */, 88F49D6C1C21E0220007422C /* isignTestAppTests */, 88F49D771C21E0220007422C /* isignTestAppUITests */, 88F49D541C21E0220007422C /* Products */, 8767BD99DB0DE852EFDCE52D /* Pods */, D851D399435A30E964E93BD3 /* Frameworks */, ); sourceTree = ""; }; 88F49D541C21E0220007422C /* Products */ = { isa = PBXGroup; children = ( 88F49D531C21E0220007422C /* isignTestApp.app */, 88F49D691C21E0220007422C /* isignTestAppTests.xctest */, 88F49D741C21E0220007422C /* isignTestAppUITests.xctest */, ); name = Products; sourceTree = ""; }; 88F49D551C21E0220007422C /* isignTestApp */ = { isa = PBXGroup; children = ( 88F49D561C21E0220007422C /* AppDelegate.swift */, 88F49D581C21E0220007422C /* FirstViewController.swift */, 88F49D5A1C21E0220007422C /* SecondViewController.swift */, 88F49D5C1C21E0220007422C /* Main.storyboard */, 88F49D5F1C21E0220007422C /* Assets.xcassets */, 88F49D611C21E0220007422C /* LaunchScreen.storyboard */, 88F49D641C21E0220007422C /* Info.plist */, ); path = isignTestApp; sourceTree = ""; }; 88F49D6C1C21E0220007422C /* isignTestAppTests */ = { isa = PBXGroup; children = ( 88F49D6D1C21E0220007422C /* isignTestAppTests.swift */, 88F49D6F1C21E0220007422C /* Info.plist */, ); path = isignTestAppTests; sourceTree = ""; }; 88F49D771C21E0220007422C /* isignTestAppUITests */ = { isa = PBXGroup; children = ( 88F49D781C21E0220007422C /* isignTestAppUITests.swift */, 88F49D7A1C21E0220007422C /* Info.plist */, ); path = isignTestAppUITests; sourceTree = ""; }; D851D399435A30E964E93BD3 /* Frameworks */ = { isa = PBXGroup; children = ( 482C83E341072C026C4E2D04 /* Pods_isignTestApp.framework */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 88F49D521C21E0220007422C /* isignTestApp */ = { isa = PBXNativeTarget; buildConfigurationList = 88F49D7D1C21E0220007422C /* Build configuration list for PBXNativeTarget "isignTestApp" */; buildPhases = ( 02F23A3A072023BC469E4D28 /* Check Pods Manifest.lock */, 88F49D4F1C21E0220007422C /* Sources */, 88F49D501C21E0220007422C /* Frameworks */, 88F49D511C21E0220007422C /* Resources */, 83318A2D9A5203CEFF1F1C66 /* Embed Pods Frameworks */, 239CFA05A032CAB39434E007 /* Copy Pods Resources */, ); buildRules = ( ); dependencies = ( ); name = isignTestApp; productName = isignTestApp; productReference = 88F49D531C21E0220007422C /* isignTestApp.app */; productType = "com.apple.product-type.application"; }; 88F49D681C21E0220007422C /* isignTestAppTests */ = { isa = PBXNativeTarget; buildConfigurationList = 88F49D801C21E0220007422C /* Build configuration list for PBXNativeTarget "isignTestAppTests" */; buildPhases = ( 88F49D651C21E0220007422C /* Sources */, 88F49D661C21E0220007422C /* Frameworks */, 88F49D671C21E0220007422C /* Resources */, ); buildRules = ( ); dependencies = ( 88F49D6B1C21E0220007422C /* PBXTargetDependency */, ); name = isignTestAppTests; productName = isignTestAppTests; productReference = 88F49D691C21E0220007422C /* isignTestAppTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; 88F49D731C21E0220007422C /* isignTestAppUITests */ = { isa = PBXNativeTarget; buildConfigurationList = 88F49D831C21E0220007422C /* Build configuration list for PBXNativeTarget "isignTestAppUITests" */; buildPhases = ( 88F49D701C21E0220007422C /* Sources */, 88F49D711C21E0220007422C /* Frameworks */, 88F49D721C21E0220007422C /* Resources */, ); buildRules = ( ); dependencies = ( 88F49D761C21E0220007422C /* PBXTargetDependency */, ); name = isignTestAppUITests; productName = isignTestAppUITests; productReference = 88F49D741C21E0220007422C /* isignTestAppUITests.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 88F49D4B1C21E0220007422C /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0720; LastUpgradeCheck = 0720; ORGANIZATIONNAME = "Sauce Labs"; TargetAttributes = { 88F49D521C21E0220007422C = { CreatedOnToolsVersion = 7.2; }; 88F49D681C21E0220007422C = { CreatedOnToolsVersion = 7.2; TestTargetID = 88F49D521C21E0220007422C; }; 88F49D731C21E0220007422C = { CreatedOnToolsVersion = 7.2; TestTargetID = 88F49D521C21E0220007422C; }; }; }; buildConfigurationList = 88F49D4E1C21E0220007422C /* Build configuration list for PBXProject "isignTestApp" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 88F49D4A1C21E0220007422C; productRefGroup = 88F49D541C21E0220007422C /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 88F49D521C21E0220007422C /* isignTestApp */, 88F49D681C21E0220007422C /* isignTestAppTests */, 88F49D731C21E0220007422C /* isignTestAppUITests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 88F49D511C21E0220007422C /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 88F49D631C21E0220007422C /* LaunchScreen.storyboard in Resources */, 88F49D601C21E0220007422C /* Assets.xcassets in Resources */, 88F49D5E1C21E0220007422C /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 88F49D671C21E0220007422C /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 88F49D721C21E0220007422C /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 02F23A3A072023BC469E4D28 /* Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Check Pods Manifest.lock"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; 239CFA05A032CAB39434E007 /* Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-isignTestApp/Pods-isignTestApp-resources.sh\"\n"; showEnvVarsInLog = 0; }; 83318A2D9A5203CEFF1F1C66 /* Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Embed Pods Frameworks"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-isignTestApp/Pods-isignTestApp-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 88F49D4F1C21E0220007422C /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 88F49D5B1C21E0220007422C /* SecondViewController.swift in Sources */, 88F49D571C21E0220007422C /* AppDelegate.swift in Sources */, 88F49D591C21E0220007422C /* FirstViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 88F49D651C21E0220007422C /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 88F49D6E1C21E0220007422C /* isignTestAppTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 88F49D701C21E0220007422C /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 88F49D791C21E0220007422C /* isignTestAppUITests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 88F49D6B1C21E0220007422C /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 88F49D521C21E0220007422C /* isignTestApp */; targetProxy = 88F49D6A1C21E0220007422C /* PBXContainerItemProxy */; }; 88F49D761C21E0220007422C /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 88F49D521C21E0220007422C /* isignTestApp */; targetProxy = 88F49D751C21E0220007422C /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 88F49D5C1C21E0220007422C /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 88F49D5D1C21E0220007422C /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 88F49D611C21E0220007422C /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 88F49D621C21E0220007422C /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 88F49D7B1C21E0220007422C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.2; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 88F49D7C1C21E0220007422C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.2; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; }; name = Release; }; 88F49D7E1C21E0220007422C /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 568EA0A65D28521957385887 /* Pods-isignTestApp.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = isignTestApp/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.saucelabs.isignTestApp; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 88F49D7F1C21E0220007422C /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 1A03A6031C2E68A83409A36A /* Pods-isignTestApp.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = isignTestApp/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.saucelabs.isignTestApp; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; 88F49D811C21E0220007422C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; INFOPLIST_FILE = isignTestAppTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.saucelabs.isignTestAppTests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/isignTestApp.app/isignTestApp"; }; name = Debug; }; 88F49D821C21E0220007422C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; INFOPLIST_FILE = isignTestAppTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.saucelabs.isignTestAppTests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/isignTestApp.app/isignTestApp"; }; name = Release; }; 88F49D841C21E0220007422C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { INFOPLIST_FILE = isignTestAppUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.saucelabs.isignTestAppUITests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_TARGET_NAME = isignTestApp; USES_XCTRUNNER = YES; }; name = Debug; }; 88F49D851C21E0220007422C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { INFOPLIST_FILE = isignTestAppUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.saucelabs.isignTestAppUITests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_TARGET_NAME = isignTestApp; USES_XCTRUNNER = YES; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 88F49D4E1C21E0220007422C /* Build configuration list for PBXProject "isignTestApp" */ = { isa = XCConfigurationList; buildConfigurations = ( 88F49D7B1C21E0220007422C /* Debug */, 88F49D7C1C21E0220007422C /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 88F49D7D1C21E0220007422C /* Build configuration list for PBXNativeTarget "isignTestApp" */ = { isa = XCConfigurationList; buildConfigurations = ( 88F49D7E1C21E0220007422C /* Debug */, 88F49D7F1C21E0220007422C /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 88F49D801C21E0220007422C /* Build configuration list for PBXNativeTarget "isignTestAppTests" */ = { isa = XCConfigurationList; buildConfigurations = ( 88F49D811C21E0220007422C /* Debug */, 88F49D821C21E0220007422C /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 88F49D831C21E0220007422C /* Build configuration list for PBXNativeTarget "isignTestAppUITests" */ = { isa = XCConfigurationList; buildConfigurations = ( 88F49D841C21E0220007422C /* Debug */, 88F49D851C21E0220007422C /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 88F49D4B1C21E0220007422C /* Project object */; } ================================================ FILE: tests/isignTestAppWithFrameworks/isignTestApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: tests/isignTestAppWithFrameworks/isignTestApp.xcodeproj/xcuserdata/neilk.xcuserdatad/xcschemes/isignTestApp.xcscheme ================================================ ================================================ FILE: tests/isignTestAppWithFrameworks/isignTestApp.xcodeproj/xcuserdata/neilk.xcuserdatad/xcschemes/xcschememanagement.plist ================================================ SchemeUserState isignTestApp.xcscheme orderHint 0 SuppressBuildableAutocreation 88F49D521C21E0220007422C primary 88F49D681C21E0220007422C primary 88F49D731C21E0220007422C primary ================================================ FILE: tests/isignTestAppWithFrameworks/isignTestApp.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: tests/isignTestAppWithFrameworks/isignTestAppTests/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 ================================================ FILE: tests/isignTestAppWithFrameworks/isignTestAppTests/isignTestAppTests.swift ================================================ // // isignTestAppTests.swift // isignTestAppTests // // Copyright © 2015 Sauce Labs. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // import XCTest @testable import isignTestApp class isignTestAppTests: XCTestCase { override func setUp() { super.setUp() // Put setup code here. This method is called before the invocation of each test method in the class. } override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. super.tearDown() } func testExample() { // This is an example of a functional test case. // Use XCTAssert and related functions to verify your tests produce the correct results. } func testPerformanceExample() { // This is an example of a performance test case. self.measureBlock { // Put the code you want to measure the time of here. } } } ================================================ FILE: tests/isignTestAppWithFrameworks/isignTestAppUITests/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 ================================================ FILE: tests/isignTestAppWithFrameworks/isignTestAppUITests/isignTestAppUITests.swift ================================================ // // isignTestAppUITests.swift // isignTestAppUITests // // Copyright © 2015 Sauce Labs. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // import XCTest class isignTestAppUITests: XCTestCase { override func setUp() { super.setUp() // Put setup code here. This method is called before the invocation of each test method in the class. // In UI tests it is usually best to stop immediately when a failure occurs. continueAfterFailure = false // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. XCUIApplication().launch() // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. } override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. super.tearDown() } func testExample() { // Use recording to get started writing UI tests. // Use XCTAssert and related functions to verify your tests produce the correct results. } } ================================================ FILE: tests/isign_base_test.py ================================================ from os.path import dirname, exists, join, isdir from isign import isign import logging from monitor_temp_file import MonitorTempFile import os import shutil import tempfile import unittest log = logging.getLogger(__name__) class IsignBaseTest(unittest.TestCase): TEST_DIR = dirname(__file__) TEST_APP = join(TEST_DIR, 'Test.app') TEST_APP_CODESIG_STR = join(TEST_DIR, 'Test.app.codesig.construct.txt') TEST_APPZIP = TEST_APP + '.zip' TEST_IPA = join(TEST_DIR, 'Test.ipa') TEST_WITH_FRAMEWORKS_IPA = join(TEST_DIR, 'TestWithFrameworks.ipa') TEST_NONAPP_DIR = join(TEST_DIR, 'NotAnAppDir') TEST_NONAPP_TXT = join(TEST_DIR, 'NotAnApp.txt') TEST_NONAPP_IPA = join(TEST_DIR, 'NotAnApp.ipa') TEST_SIMULATOR_APP = join(TEST_DIR, 'TestSimulator.app.zip') KEY = join(TEST_DIR, 'credentials', 'test.key.pem') CERTIFICATE = join(TEST_DIR, 'credentials', 'test.cert.pem') PROVISIONING_PROFILE = join(TEST_DIR, 'credentials', 'test.mobileprovision') ERROR_KEY = '_errors' CREDENTIALS_DIR = join(TEST_DIR, 'credentials_std_names') CREDENTIALS_DIR_2 = join(TEST_DIR, 'credentials_std_names_2') TEST_UNSIGNED_THIN_APP = join(TEST_DIR, 'Test_unsigned_thin.app') TEST_UNSIGNED_FAT_APP = join(TEST_DIR, 'Test_unsigned_fat.app') # Fake Apple organizational unit OU = 'ISIGNTESTS' def setUp(self): """ this helps us monitor if we're not cleaning up temp files """ MonitorTempFile.start() def tearDown(self): """ remove monitor on tempfile creation """ remaining_temp_files = MonitorTempFile.get_temp_files() MonitorTempFile.stop() if len(remaining_temp_files) != 0: log.error("remaining temp files: %s", ', '.join(remaining_temp_files)) # assert len(remaining_temp_files) == 0 def resign(self, filename, **args): """ resign with test credentials """ args.update({ "key": self.KEY, "certificate": self.CERTIFICATE, "provisioning_profile": self.PROVISIONING_PROFILE }) return isign.resign(filename, **args) def unlink(self, path): if exists(path): if isdir(path): shutil.rmtree(path) else: os.unlink(path) def get_temp_file(self, prefix='isign-test-'): """ just getting a file path that probably isn't in use """ (fd, path) = tempfile.mkstemp(prefix=prefix) os.close(fd) os.unlink(path) return path def get_temp_dir(self, prefix='isign-test-'): return tempfile.mkdtemp(prefix=prefix) ================================================ FILE: tests/monitor_temp_file.py ================================================ """" isign creates big temporary files, using the standard tempfile library. If they are not cleaned up, they can fill up the disk. This has already happened in production. :( This library monkey-patches tempfile to use our own temporary directory, so it's easy to test that we aren't leaving any temp files behind. """ import os import shutil import tempfile class MonitorTempFile(object): TEMP_DIR = None @classmethod def mkdtemp(cls, *args, **kwargs): """ ensure temp directories are subdirs of TEMP_DIR """ kwargs['dir'] = MonitorTempFile.TEMP_DIR return tempfile._original_mkdtemp(*args, **kwargs) @classmethod def mkstemp(cls, *args, **kwargs): """ ensure temp files are within TEMP_DIR """ kwargs['dir'] = MonitorTempFile.TEMP_DIR return tempfile._original_mkstemp(*args, **kwargs) @classmethod def NamedTemporaryFile(cls, *args, **kwargs): """ ensure named temp files are within TEMP_DIR """ kwargs['dir'] = MonitorTempFile.TEMP_DIR return tempfile._original_NamedTemporaryFile(*args, **kwargs) @classmethod def start(cls): """ swap a few methods in tempfile with our versions that limit them to a particular directory """ if hasattr(tempfile, '_is_patched') and tempfile._is_patched: raise Exception("need tempfile to be in unpatched state!") cls.TEMP_DIR = tempfile.mkdtemp(prefix='isign-test-run-') tempfile._original_mkdtemp = tempfile.mkdtemp tempfile.mkdtemp = MonitorTempFile.mkdtemp tempfile._original_mkstemp = tempfile.mkstemp tempfile.mkstemp = MonitorTempFile.mkstemp tempfile._original_NamedTemporaryFile = tempfile.NamedTemporaryFile tempfile.NamedTemporaryFile = MonitorTempFile.NamedTemporaryFile tempfile._is_patched = True @classmethod def stop(cls): """ restore a few methods in tempfile. opposite of _tempfile_patch """ tempfile.mkdtemp = tempfile._original_mkdtemp tempfile.mkstemp = tempfile._original_mkstemp tempfile.NamedTemporaryFile = tempfile._original_NamedTemporaryFile tempfile._is_patched = False shutil.rmtree(cls.TEMP_DIR) cls.TEMP_DIR = None @classmethod def get_temp_files(cls): return os.listdir(cls.TEMP_DIR) @classmethod def has_no_temp_files(cls): """ check if this test has created any temp files which aren't cleaned up """ if cls.TEMP_DIR is None: raise Exception("temp dir is None. Maybe call patch() first?") return len(cls.get_temp_files()) == 0 ================================================ FILE: tests/sample-entitlements.plist ================================================ application-identifier ISIGNTEST.com.example.isigntest com.apple.developer.team-identifier ISIGNTEST get-task-allow keychain-access-groups ISIGNTEST.com.example.isigntest ================================================ FILE: tests/test_archive.py ================================================ from isign_base_test import IsignBaseTest from isign.archive import archive_factory, Archive, AppArchive, AppZipArchive, IpaArchive import logging log = logging.getLogger(__name__) class TestArchive(IsignBaseTest): def _test_good(self, filename, klass): archive = archive_factory(filename) assert archive is not None assert archive.__class__ is klass assert isinstance(archive, Archive) def test_archive_factory_app(self): self._test_good(self.TEST_APP, AppArchive) def test_archive_factory_appzip(self): self._test_good(self.TEST_APPZIP, AppZipArchive) def test_archive_factory_ipa(self): self._test_good(self.TEST_IPA, IpaArchive) def test_archive_factory_nonapp_dir(self): archive = archive_factory(self.TEST_NONAPP_DIR) assert archive is None def test_archive_factory_nonapp_ipa(self): archive = archive_factory(self.TEST_NONAPP_IPA) assert archive is None def test_archive_factory_nonapp_txt(self): archive = archive_factory(self.TEST_NONAPP_TXT) assert archive is None def test_archive_factory_nonapp_simulator_app(self): archive = archive_factory(self.TEST_SIMULATOR_APP) assert archive is None class TestBundleInfo(IsignBaseTest): def _test_bundle_info(self, filename): archive = archive_factory(filename) assert archive is not None assert archive.bundle_info is not None assert archive.bundle_info['CFBundleName'] == 'isignTestApp' def test_app_archive_info(self): self._test_bundle_info(self.TEST_APP) def test_appzip_archive_info(self): self._test_bundle_info(self.TEST_APPZIP) def test_ipa_archive_info(self): self._test_bundle_info(self.TEST_IPA) class TestArchivePrecheck(IsignBaseTest): def test_precheck_app(self): assert AppArchive.precheck(self.TEST_APP) def test_precheck_appzip(self): assert AppZipArchive.precheck(self.TEST_APPZIP) def test_precheck_ipa(self): assert IpaArchive.precheck(self.TEST_IPA) def test_bad_precheck_app(self): assert AppArchive.precheck(self.TEST_NONAPP_DIR) is False assert AppArchive.precheck(self.TEST_APPZIP) is False assert AppArchive.precheck(self.TEST_IPA) is False def test_bad_precheck_appzip(self): assert AppZipArchive.precheck(self.TEST_APP) is False assert AppZipArchive.precheck(self.TEST_IPA) is False def test_bad_precheck_ipa(self): assert IpaArchive.precheck(self.TEST_APP) is False assert IpaArchive.precheck(self.TEST_APPZIP) is False assert IpaArchive.precheck(self.TEST_NONAPP_IPA) is False ================================================ FILE: tests/test_creds_dir.py ================================================ from isign.exceptions import MissingCredentials from isign_base_test import IsignBaseTest from isign import isign import os from os.path import exists, join import logging log = logging.getLogger(__name__) class TestCredentialsDir(IsignBaseTest): def test_creds_dir(self): # this directory contains credentials with the standard names # key.pem, certificate.pem, isign.mobileprovision output_path = self.get_temp_file() isign.resign_with_creds_dir(self.TEST_IPA, self.CREDENTIALS_DIR, output_path=output_path) assert exists(output_path) assert os.path.getsize(output_path) > 0 self.unlink(output_path) def test_bad_creds_dir(self): # while this contains credentials, they don't have standard names credentials_dir = join(self.TEST_DIR, 'credentials') output_path = self.get_temp_file() with self.assertRaises(MissingCredentials): isign.resign_with_creds_dir(self.TEST_IPA, credentials_dir, output_path=output_path) self.unlink(output_path) ================================================ FILE: tests/test_entitlements.py ================================================ from isign_base_test import IsignBaseTest from isign.bundle import App import logging log = logging.getLogger(__name__) class TestEntitlements(IsignBaseTest): def test_entitlements_extraction(self): entitlements = App.extract_entitlements(self.PROVISIONING_PROFILE) log.debug(entitlements) assert entitlements['application-identifier'] == 'ISIGNTESTS.*' assert entitlements['get-task-allow'] == True ================================================ FILE: tests/test_helpers.py ================================================ import isign.archive from isign.archive import AppZipArchive from isign.exceptions import MissingHelpers from isign_base_test import IsignBaseTest from distutils import spawn import logging log = logging.getLogger(__name__) class MissingHelpersArchive(AppZipArchive): """ An App whose helpers are not present """ helpers = ['a_file_that_should_never_be_present'] def dummy_find_executable(name): return '/dummy/path/to/name' class TestHelpers(IsignBaseTest): def test_helpers_is_present(self): """ test that missing helpers raises exception """ with self.assertRaises(MissingHelpers): MissingHelpersArchive.precheck(self.TEST_APPZIP) def test_helpers_become_present(self): """ test that we can install helpers without restart """ with self.assertRaises(MissingHelpers): MissingHelpersArchive.precheck(self.TEST_APPZIP) spawn._original_find_executable = spawn.find_executable spawn.find_executable = dummy_find_executable MissingHelpersArchive.precheck(self.TEST_APPZIP) if hasattr(spawn, '_original_find_executable'): spawn.find_executable = spawn._original_find_executable isign.archive.helper_paths = {} ================================================ FILE: tests/test_multisign.py ================================================ from isign_base_test import IsignBaseTest import os from os.path import exists from isign.multisign import multisign import logging log = logging.getLogger(__name__) class TestMultisign(IsignBaseTest): def test_multisign(self): output_path1 = self.get_temp_file() output_path2 = self.get_temp_file() creds_dir_to_output_paths = { self.CREDENTIALS_DIR: output_path1, self.CREDENTIALS_DIR_2: output_path2 } results = multisign(self.TEST_IPA, creds_dir_to_output_paths) log.debug("results: %s", results) for output_path in [output_path1, output_path2]: assert exists(output_path) assert os.path.getsize(output_path) > 0 self.unlink(output_path) ================================================ FILE: tests/test_parsing.py ================================================ from isign_base_test import IsignBaseTest import isign.bundle from isign.signable import Executable import logging log = logging.getLogger(__name__) class TestParsing(IsignBaseTest): """ This tests whether code signatures are parsed, by comparing stringified parses.""" # Tests the parse-before-resign functionality used in # bin/pprint_codesig, which isn't exposed nicely as such. # Also see generate_codesig_construct_txt.py, to generate # the string this tests for def test_app(self): with open(self.TEST_APP_CODESIG_STR, 'r') as f: expected_codesig_str = f.read().strip() bundle = isign.bundle.App(self.TEST_APP) executable = Executable(bundle, bundle.get_executable_path(), None) arch = executable.arches[0] codesig_str = str(arch['cmds']['LC_CODE_SIGNATURE']) self.assertEquals(expected_codesig_str, codesig_str) ================================================ FILE: tests/test_public_interface.py ================================================ from isign_base_test import IsignBaseTest import os from os.path import exists from isign import isign import logging log = logging.getLogger(__name__) class TestPublicInterface(IsignBaseTest): def _test_signable(self, filename, output_path): self.resign(filename, output_path=output_path) assert exists(output_path) assert os.path.getsize(output_path) > 0 self.unlink(output_path) def _test_unsignable(self, filename, output_path): with self.assertRaises(isign.NotSignable): self.resign(filename, output_path=output_path) self.unlink(output_path) def test_app(self): self._test_signable(self.TEST_APP, self.get_temp_dir()) def test_app_ipa(self): self._test_signable(self.TEST_IPA, self.get_temp_file()) def test_app_with_frameworks_ipa(self): self._test_signable(self.TEST_WITH_FRAMEWORKS_IPA, self.get_temp_file()) def test_appzip(self): self._test_signable(self.TEST_APPZIP, self.get_temp_file()) def test_non_app_txt(self): self._test_unsignable(self.TEST_NONAPP_TXT, self.get_temp_file()) def test_non_app_ipa(self): self._test_unsignable(self.TEST_NONAPP_IPA, self.get_temp_file()) def test_simulator_app(self): self._test_unsignable(self.TEST_SIMULATOR_APP, self.get_temp_file()) ================================================ FILE: tests/test_sig.py ================================================ import isign from isign_base_test import IsignBaseTest from os.path import join class TestSigner(IsignBaseTest): def test_bad_signature(self): """ make openssl appear to return a bad signature """ old_openssl = isign.signer.OPENSSL try: signer = isign.signer.Signer( signer_key_file=self.KEY, signer_cert_file=self.CERTIFICATE, apple_cert_file=isign.isign.DEFAULT_APPLE_CERT_PATH) isign.signer.OPENSSL = join(self.TEST_DIR, "bad_openssl") with self.assertRaises(Exception): signer.sign("some data") finally: isign.signer.OPENSSL = old_openssl ================================================ FILE: tests/test_versioning.py ================================================ #!/usr/bin/env python import os.path import importlib import unittest tests_dir = os.path.abspath(os.path.dirname(__file__)) package_name = tests_dir.split(os.path.sep)[-2].replace('-', '_') package = importlib.import_module(package_name) class VersioningTestCase(unittest.TestCase): def assert_proper_attribute(self, attribute): try: assert getattr(package, attribute), ( "{} improperly set".format(attribute)) except AttributeError: assert False, "missing {}".format(attribute) def test_version_attribute(self): self.assert_proper_attribute("__version__") # test major, minor, and patch are numbers version_split = package.__version__.split(".")[:3] assert version_split, "__version__ is not set" for n in version_split: try: int(n) except ValueError: assert False, "'{}' is not an integer".format(n) def test_commit_attribute(self): self.assert_proper_attribute("__commit__") def test_build_attribute(self): self.assert_proper_attribute("__build__") if __name__ == '__main__': unittest.main() ================================================ FILE: tests/test_versus_apple.py ================================================ from distutils import spawn from isign_base_test import IsignBaseTest import logging from nose.plugins.skip import SkipTest import os from os.path import join import platform import re import shutil import subprocess import tempfile import zipfile CODESIGN_BIN = spawn.find_executable('codesign') log = logging.getLogger(__name__) class TestVersusApple(IsignBaseTest): def codesign_display(self, path): """ inspect a path with codesign """ cmd = [CODESIGN_BIN, '-d', '-r-', '--verbose=20', path] # n.b. codesign may print things to STDERR, or STDOUT, depending # on exactly what you're extracting. I KNOW RIGHT? proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) out, _ = proc.communicate() if proc.returncode != 0: log.debug(out) log.debug(_) assert proc.returncode == 0, "Return code not 0" return self.codesign_display_parse(out) def codesign_display_parse(self, out): """ Parse codesign output into a dict. The output format is XML-like, in that it's a tree of nodes of varying types (including key-val pairs). We are assuming that it never gets more than 1 level deep (so, "array line" is just a special case here) """ # designated => identifier "com.lyft.ios.enterprise.dev" and anchor... text_line = re.compile('^(\w[\w\s.]+) => (.*)$') # CodeDirectory v=20200 size=79151 flags=0x0(none) hashes=3948+5 ... props_line = re.compile('^(\w[\w\s.]+)\s+((?:\w+=\S+\s*)+)$') # Signed Time=May 14, 2015, 7:12:25 PM # Info.plist=not bound single_prop_line = re.compile('(\w[\w\s.]+)=(.*)$') # this assumes we only have one level of sub-arrays # -3=969d263f74a5755cd3b4bede3f9e90c9fb0b7bca array_line = re.compile('\s+(-?\d+)=(.*)$') # last node assigned - used for appending sub-arrays, if encountered last = None ret = {} for line in out.splitlines(): key = None val = None text_match = text_line.match(line) props_match = props_line.match(line) sp_match = single_prop_line.match(line) array_match = array_line.match(line) if text_match: key = text_match.group(1) val = text_match.group(2) elif props_match: key = props_match.group(1) val = {} pairs = re.split('\s+', props_match.group(2)) for pair in pairs: pairmatch = re.match('(\w+)=(\S+)', pair) pairkey = pairmatch.group(1) pairval = pairmatch.group(2) val[pairkey] = pairval elif sp_match: key = sp_match.group(1) val = sp_match.group(2) elif array_match: if '_' not in last: last['_'] = {} akey = array_match.group(1) aval = array_match.group(2) last['_'][akey] = aval else: # probably an error of some kind. These # get appended into the output too. :( if self.ERROR_KEY not in ret: ret[self.ERROR_KEY] = [] ret[self.ERROR_KEY].append(line) if key is not None: if key not in ret: ret[key] = [] ret[key].append(val) last = val return ret def get_dict_with_key(self, x, key): """ check a list for a dict that has a key """ # e.g. if x = [ { 'a': 1 }, { 'b': 2 }] # and key = 'a' # return 1 for item in x: if key in item: return item[key] return None def get_hashes_from_codesign_output_info(self, info): try: return self.get_dict_with_key(info['Hash'], '_') or info['Page'][0]['_'] except KeyError: return None def assert_common_signed_properties(self, info): # has an executable assert 'Executable' in info # has an identifier assert 'Identifier' in info # has a codedirectory, embedded assert 'CodeDirectory' in info codedirectory_info = info['CodeDirectory'][0] assert codedirectory_info['location'] == 'embedded' # has a set of hashes assert 'Hash' in info hashes = self.get_hashes_from_codesign_output_info(info) assert hashes is not None # seal hash assert 'CDHash' in info # signed assert 'Signature' in info assert 'Authority' in info # The following only works with a cert signed by apple # # if isinstance(info['Authority'], list): # authorities = info['Authority'] # else: # authorities = [info['Authority']] # assert 'Apple Root CA' in authorities assert 'Info.plist' in info assert self.get_dict_with_key(info['Info.plist'], 'entries') is not None assert 'TeamIdentifier' in info # TODO get this from an arg assert info['TeamIdentifier'][0] == self.OU assert 'designated' in info assert 'anchor apple generic' in info['designated'][0] # should have no errors assert self.ERROR_KEY not in info def assert_common_signed_hashes(self, info, start_index, end_index): assert 'Hash' in info hashes = self.get_hashes_from_codesign_output_info(info) assert hashes is not None for i in range(start_index, end_index + 1): assert str(i) in hashes return hashes def assert_hashes_for_signable(self, info, hashes_to_check): """ check that various hashes look right. """ # Most of the hashes in the Hash section are hashes of blocks of the # object code in question. These all have positive subscripts. # But the "special" slots use negative numbers, and # are hashes of: # -5 Embedded entitlement configuration slot # -4 App-specific slot (in all the examples we know of, all zeroes) # -3 Resource Directory slot # -2 Requirements slot # -1 Info.plist slot # For more info, see codedirectory.h in Apple open source, e.g. # http://opensource.apple.com/source/libsecurity_codesigning/ # libsecurity_codesigning-55032/lib/codedirectory.h assert 'Hash' in info hashes = self.get_hashes_from_codesign_output_info(info) assert hashes is not None for i in hashes_to_check: key = str(i) assert key in hashes assert int(hashes[key], 16) != 0 def assert_matching_identifier(self, app_path, expected): info = self.codesign_display(app_path) identifier = info['Identifier'][0] assert identifier == expected def check_bundle(self, path): """ look at info for bundles (apps and frameworks) """ info = self.codesign_display(path) self.assert_common_signed_properties(info) assert 'Sealed Resources' in info self.assert_hashes_for_signable(info, [-5, -3, -2, -1]) # TODO subject.CN from cert? def check_dylib(self, path): info = self.codesign_display(path) self.assert_common_signed_properties(info) self.assert_hashes_for_signable(info, [-2, -1]) def test_override_identifier(self): """ Resign an app with identifiers of varying lengths, test that they were signed correctly with the new identifier """ # skip if this isn't a Mac with codesign installed if platform.system() != 'Darwin' or CODESIGN_BIN is None: raise SkipTest # old_cwd = os.getcwd() info = self.codesign_display(self.TEST_APP) original_id = info['Identifier'][0] # Make sure our original ID is long enough to test shorter bundle ids assert len(original_id) >= 6 alphabet = 'abcdefghijklmnopqrstuvwxyz' while len(alphabet) <= len(original_id): alphabet += alphabet # Test with a shorter bundle ID short_id = alphabet[0:len(original_id) / 2 + 1] working_dir = tempfile.mkdtemp() os.chdir(working_dir) resigned_app_path = join(working_dir, 'Short.app') self.resign(self.TEST_APP, output_path=resigned_app_path, info_props={ 'CFBundleIdentifier': short_id }) self.assert_matching_identifier(resigned_app_path, short_id) shutil.rmtree(working_dir) # Test with a longer bundle ID long_id = alphabet[0:len(original_id) + 1] working_dir = tempfile.mkdtemp() os.chdir(working_dir) resigned_app_path = join(working_dir, 'Long.app') self.resign(self.TEST_APP, output_path=resigned_app_path, info_props={ 'CFBundleIdentifier': long_id }) self.assert_matching_identifier(resigned_app_path, long_id) # os.chdir(old_cwd) shutil.rmtree(working_dir) def test_app(self): """ Extract a resigned app with frameworks, analyze if some expected things about them are true """ # skip if this isn't a Mac with codesign installed if platform.system() != 'Darwin' or CODESIGN_BIN is None: raise SkipTest old_cwd = os.getcwd() # resign the test app that has frameworks, extract it to a temp directory working_dir = tempfile.mkdtemp() resigned_ipa_path = join(working_dir, 'resigned.ipa') self.resign(self.TEST_WITH_FRAMEWORKS_IPA, output_path=resigned_ipa_path) os.chdir(working_dir) with zipfile.ZipFile(resigned_ipa_path) as zf: zf.extractall() # expected path to app # When we ask for codesign to analyze the app directory, it # will default to showing info for the main executable app_path = join(working_dir, 'Payload/isignTestApp.app') self.check_bundle(app_path) # Now we do similar tests for a dynamic library, linked to the # main executable. dylib_path = join(app_path, 'Frameworks', 'libswiftCore.dylib') self.check_dylib(dylib_path) # Now we do similar tests for a framework framework_path = join(app_path, 'Frameworks', 'FontAwesome_swift.framework') self.check_bundle(framework_path) os.chdir(old_cwd) shutil.rmtree(working_dir) def test_app_from_scratch(self): """ Test signing app bundles from scratch """ # skip if this isn't a Mac with codesign installed if platform.system() != 'Darwin' or CODESIGN_BIN is None: raise SkipTest working_dir = tempfile.mkdtemp() signed_app_path = join(working_dir, 'signed.app') self.resign(self.TEST_UNSIGNED_THIN_APP, output_path=signed_app_path) os.chdir(working_dir) # expected path to app # When we ask for codesign to analyze the app directory, it # will default to showing info for the main executable app_path = signed_app_path self.check_bundle(app_path) # Now we do similar tests for a dynamic library, linked to the # main executable. dylib_path = join(app_path, 'Frameworks', 'libswiftCore.dylib') self.check_dylib(dylib_path) shutil.rmtree(working_dir) working_dir = tempfile.mkdtemp() signed_app_path = join(working_dir, 'signed.app') self.resign(self.TEST_UNSIGNED_FAT_APP, output_path=signed_app_path) os.chdir(working_dir) # expected path to app # When we ask for codesign to analyze the app directory, it # will default to showing info for the main executable app_path = signed_app_path self.check_bundle(app_path) # Now we do similar tests for a dynamic library, linked to the # main executable. dylib_path = join(app_path, 'Frameworks', 'libswiftCore.dylib') self.check_dylib(dylib_path) shutil.rmtree(working_dir) ================================================ FILE: version.sh ================================================ #!/bin/bash # Figures out what the current version is, echoes that back, # and also writes a `version.json` file into the package. set -e MAJMIN_VERSION="1.6" pushd $(dirname $0) >/dev/null working_dir=$PWD name=$(basename $PWD) popd >/dev/null package=$(echo $name | sed 's/-/_/g') version_json="${working_dir}/${package}/version.json" version_suffix="" # official version if [[ "$JOB_NAME" = "${name}" ]] && [[ -n "$BUILD_TAG" ]]; then patch_version=0 if [[ -n "$(git tag --list v$MAJMIN_VERSION.0)" ]]; then # number of commits since vMAJOR.MINOR.0 patch_version=$(git rev-list --count $(git describe --tags --match "v${MAJMIN_VERSION}.0" | cut -f 1 -d -)...HEAD) fi # add post version if built before (i.e., already tagged) post_version=$(git tag --contain | wc -l | awk '{print $1}') test "$post_version" -gt 0 && version_suffix=".post${post_version}" # development version else if [[ -n "$(git tag --list 'v[0-9]*')" ]]; then recent_tag=$(git describe --tags --match 'v[0-9]*' | cut -f 1 -d -) majmin_version=$(echo $recent_tag | tr "v.-" " " | awk '{print $1"."$2}') patch_version=$(echo $recent_tag | tr "v.-" " " | awk '{print $3}') dev_version=$(git rev-list --count ${recent_tag}...HEAD) else # start of dev, nothing tagged majmin_version="0.0" patch_version="0" dev_version=$(git rev-list --count HEAD) fi version_suffix=".$(date '+%s').dev${dev_version}+${USER}" fi version="${majmin_version:-$MAJMIN_VERSION}.${patch_version}${version_suffix}" json='"version": "'$version'", "commit": "'$(git rev-parse HEAD)'", "build": "'${BUILD_TAG:-"dev"}'"' # write-out version.json echo "{${json}}" > $version_json style="$1" if [[ "$style" = "json" ]]; then cat $version_json else echo $version fi