Skip to content

Commit

Permalink
[OSX] Fixed code signing on OSX (#1110)
Browse files Browse the repository at this point in the history
Related: spesmilo#4994

OSX building wasn't code signing the bundled binaries that PyInstaller adds to the .zip embedded in the app executable.

This could potentially cause issues in the future if MacOS versions in the future require fully signed apps.
  • Loading branch information
cculianu committed Jan 16, 2019
1 parent 2622b09 commit 315d273
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 8 deletions.
4 changes: 1 addition & 3 deletions contrib/osx/make_osx
Expand Up @@ -95,7 +95,6 @@ info "Downloading libusb..."
curl https://homebrew.bintray.com/bottles/libusb-1.0.21.el_capitan.bottle.tar.gz | \
tar xz --directory $BUILDDIR
cp -fp $BUILDDIR/libusb/1.0.21/lib/libusb-1.0.dylib contrib/osx
#NOTE: Do *not* code sign these .dylibs. Doing so makes the in-app notifications no longer work for some unexplained reason.

info "Building libsecp256k1"
git clone https://github.com/bitcoin-core/secp256k1 $BUILDDIR/secp256k1
Expand All @@ -107,7 +106,6 @@ git clean -f -x -q
make
popd
cp -fp $BUILDDIR/secp256k1/.libs/libsecp256k1.0.dylib contrib/osx
#NOTE: Do *not* code sign these .dylibs. Doing so makes the in-app notifications no longer work for some unexplained reason.

info "Building CalinsQRReader..."
d=contrib/osx/CalinsQRReader
Expand All @@ -130,7 +128,7 @@ info "Building $PACKAGE..."
python3 setup.py install --user > /dev/null || fail "Could not build $PACKAGE"

info "Building binary"
pyinstaller --noconfirm --ascii --name $VERSION contrib/osx/osx.spec || fail "Could not build binary"
APP_SIGN="$APP_SIGN" pyinstaller --noconfirm --ascii --name $VERSION contrib/osx/osx.spec || fail "Could not build binary"

info "Adding bitcoincash URI types to Info.plist"
plutil -insert 'CFBundleURLTypes' \
Expand Down
52 changes: 47 additions & 5 deletions contrib/osx/osx.spec
@@ -1,15 +1,51 @@
# -*- mode: python -*-

from PyInstaller.utils.hooks import collect_data_files, collect_submodules, collect_dynamic_libs

import sys
import os
import sys, os

PACKAGE='Electron-Cash'
BUNDLE_IDENTIFIER='org.electroncash.' + PACKAGE # Used for info.plist
PYPKG='electroncash'
MAIN_SCRIPT='electron-cash'
ICONS_FILE='electron.icns'
APP_SIGN = os.environ.get('APP_SIGN', '')

def fail(*msg):
RED='\033[0;31m'
NC='\033[0m' # No Color
print("\r🗯 {}ERROR:{}".format(RED, NC), *msg)
sys.exit(1)

def codesign(identity, binary):
d = os.path.dirname(binary)
saved_dir=None
if d:
# switch to directory of the binary so codesign verbose messages don't include long path
saved_dir = os.path.abspath(os.path.curdir)
os.chdir(d)
binary = os.path.basename(binary)
os.system("codesign -v -f -s '{}' '{}'".format(identity, binary))==0 or fail("Could not code sign " + binary)
if saved_dir:
os.chdir(saved_dir)

def monkey_patch_pyinstaller_for_codesigning(identity):
# Monkey-patch PyInstaller so that we app-sign all binaries *after* they are modified by PyInstaller
# If we app-sign before that point, the signature will be invalid because PyInstaller modifies
# @loader_path in the Mach-O loader table.
try:
import PyInstaller.depend.dylib
_saved_func = PyInstaller.depend.dylib.mac_set_relative_dylib_deps
except (ImportError, NameError, AttributeError):
# Hmm. Likely wrong PyInstaller version.
fail("Could not monkey-patch PyInstaller for code signing. Please ensure that you are using PyInstaller 3.4.")
_signed = set()
def my_func(fn, distname):
_saved_func(fn, distname)
if (fn, distname) not in _signed:
codesign(identity, fn)
_signed.add((fn,distname)) # remember we signed it so we don't sign again
PyInstaller.depend.dylib.mac_set_relative_dylib_deps = my_func


for i, x in enumerate(sys.argv):
if x == '--name':
Expand Down Expand Up @@ -79,14 +115,20 @@ for d in a.datas:
# Remove QtWeb and other stuff that we know we never use.
# This is a hack of sorts that works to keep the binary file size reasonable.
bins2remove=('qtweb', 'qt3d', 'qtgame', 'qtdesigner', 'qtquick', 'qtlocation', 'qttest', 'qtxml')
print("Removing", *bins2remove)
files2remove=('libqsqlmysql.dylib', 'libdeclarative_multimedia.dylib', 'libqtquickscene2dplugin.dylib', 'libqtquickscene3dplugin.dylib')
print("Removing", *(bins2remove + files2remove))
for x in a.binaries.copy():
for r in bins2remove:
if x[0].lower().startswith(r):
if x[0].lower().startswith(r) or os.path.basename(x[1].lower()) in files2remove:
a.binaries.remove(x)
print('----> Removed:', x)
break # break from inner loop
#

# If code signing, monkey-patch in a code signing step to pyinstaller. See: https://github.com/spesmilo/electrum/issues/4994
if APP_SIGN:
monkey_patch_pyinstaller_for_codesigning(APP_SIGN)

pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)

exe = EXE(pyz,
Expand Down

0 comments on commit 315d273

Please sign in to comment.