tmac build: conform to macOS 10.15 Gatekeeper requirements - electrum - Electrum Bitcoin wallet HTML git clone https://git.parazyd.org/electrum DIR Log DIR Files DIR Refs DIR Submodules --- DIR commit 095464b620e3e1bb699d174d969526f78ad356b9 DIR parent 9baaf1afda3181cd511fda83c0be426a85c02abd HTML Author: SomberNight <somber.night@protonmail.com> Date: Tue, 12 May 2020 12:37:40 +0200 mac build: conform to macOS 10.15 Gatekeeper requirements fixes #6128 some of this is based on: https://github.com/metabrainz/picard/blob/e1354632d2db305b7a7624282701d34d73afa225/scripts/package/macos-notarize-app.sh https://github.com/Electron-Cash/Electron-Cash/commit/1eb8b71e7d11141432f1c46629683a5a703795e2 https://github.com/Electron-Cash/Electron-Cash/commit/24e44e9784fa23fa9f408ce3f9489fac8568093b https://github.com/Electron-Cash/Electron-Cash/commit/5abec73eee0cdeb725e3c5a989621ec4ccfb92a0 Diffstat: M contrib/osx/README.md | 65 ++++++++++++++++--------------- D contrib/osx/base.sh | 23 ----------------------- A contrib/osx/entitlements.plist | 19 +++++++++++++++++++ M contrib/osx/make_osx | 62 +++++++++++++++++++++++-------- A contrib/osx/notarize_app.sh | 77 +++++++++++++++++++++++++++++++ 5 files changed, 175 insertions(+), 71 deletions(-) --- DIR diff --git a/contrib/osx/README.md b/contrib/osx/README.md t@@ -1,5 +1,5 @@ -Building Mac OS binaries -======================== +Building macOS binaries +======================= ✗ _This script does not produce reproducible output (yet!). Please help us remedy this._ t@@ -7,36 +7,47 @@ Building Mac OS binaries This guide explains how to build Electrum binaries for macOS systems. -## 1. Building the binary +## Building the binary -This needs to be done on a system running macOS or OS X. We use El Capitan (10.11.6) as building it -on High Sierra (or later) -makes the binaries [incompatible with older versions](https://github.com/pyinstaller/pyinstaller/issues/1191). +This needs to be done on a system running macOS or OS X. -Another factor for the minimum supported macOS version is the -[bundled Qt version](https://github.com/spesmilo/electrum/issues/3685). +Notes about compatibility with different macOS versions: +- In general the binary is not guaranteed to run on an older version of macOS + than what the build machine has. This is due to bundling the compiled Python into + the [PyInstaller binary](https://github.com/pyinstaller/pyinstaller/issues/1191). +- The [bundled version of Qt](https://github.com/spesmilo/electrum/issues/3685) also + imposes a minimum supported macOS version. +- If you want to build binaries that conform to the macOS "Gatekeeper", so as to + minimise the warnings users get, the binaries need to be codesigned with a + certificate issued by Apple, and starting with macOS 10.15 the binaries also + need to be notarized by Apple's central server. The catch is that to be able to build + binaries that Apple will notarise (due to the requirements on the binaries themselves, + e.g. hardened runtime) the build machine needs at least macOS 10.14. + See [#6128](https://github.com/spesmilo/electrum/issues/6128). + +We currently build the release binaries on macOS 10.14.6, and these seem to run on +10.13 or newer. Before starting, make sure that the Xcode command line tools are installed (e.g. you have `git`). -#### 1.1a Get Xcode +#### 1.a Get Xcode Building the QR scanner (CalinsQRReader) requires full Xcode (not just command line tools). -The last Xcode version compatible with El Capitan is Xcode 8.2.1 - Get it from [here](https://developer.apple.com/download/more/). - Unfortunately, you need an "Apple ID" account. +(note: the last Xcode that runs on macOS 10.14.6 is Xcode 11.3.1) + After downloading, uncompress it. Make sure it is the "selected" xcode (e.g.): sudo xcode-select -s $HOME/Downloads/Xcode.app/Contents/Developer/ -#### 1.1b Build QR scanner separately on newer Mac +#### 1.b Build QR scanner separately on another Mac -Alternatively, you can try building just the QR scanner on newer macOS. +Alternatively, you can try building just the QR scanner on another Mac. On newer Mac, run: t@@ -46,27 +57,17 @@ On newer Mac, run: Move `prebuilt_qr` to El Capitan: `contrib/osx/CalinsQRReader/prebuilt_qr`. -#### 1.2 Build Electrum +#### 2. Build Electrum cd electrum ./contrib/osx/make_osx - -This creates both a folder named Electrum.app and the .dmg file. - -## 2. Building the image deterministically (WIP) -The usual way to distribute macOS applications is to use image files containing the -application. Although these images can be created on a Mac with the built-in `hdiutil`, -they are not deterministic. - -Instead, we use the toolchain that Bitcoin uses: genisoimage and libdmg-hfsplus. -These tools do not work on macOS, so you need a separate Linux machine (or VM). - -Copy the Electrum.app directory over and install the dependencies, e.g.: +This creates both a folder named Electrum.app and the .dmg file. - apt install libcap-dev cmake make gcc faketime - -Then you can just invoke `package.sh` with the path to the app: +If you want the binaries codesigned for MacOS and notarised by Apple's central server, +provide these env vars to the `make_osx` script: - cd electrum - ./contrib/osx/package.sh ~/Electrum.app/ + CODESIGN_CERT="Developer ID Application: Electrum Technologies GmbH (L6P37P7P56)" \ + APPLE_ID_USER="me@email.com" \ + APPLE_ID_PASSWORD="1234" \ + ./contrib/osx/make_osx DIR diff --git a/contrib/osx/base.sh b/contrib/osx/base.sh t@@ -1,23 +0,0 @@ -#!/usr/bin/env bash - -. $(dirname "$0")/../build_tools_util.sh - - -function DoCodeSignMaybe { # ARGS: infoName fileOrDirName codesignIdentity - infoName="$1" - file="$2" - identity="$3" - deep="" - if [ -z "$identity" ]; then - # we are ok with them not passing anything; master script calls us unconditionally even if no identity is specified - return - fi - if [ -d "$file" ]; then - deep="--deep" - fi - if [ -z "$infoName" ] || [ -z "$file" ] || [ -z "$identity" ] || [ ! -e "$file" ]; then - fail "Argument error to internal function DoCodeSignMaybe()" - fi - info "Code signing ${infoName}..." - codesign -f -v $deep -s "$identity" "$file" || fail "Could not code sign ${infoName}" -} DIR diff --git a/contrib/osx/entitlements.plist b/contrib/osx/entitlements.plist t@@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <!-- These are required for binaries built by PyInstaller --> + <!-- see pyinstaller/pyinstaller#4629 --> + <key>com.apple.security.cs.allow-unsigned-executable-memory</key> + <true/> + <key>com.apple.security.cs.disable-library-validation</key> + <true/> + + <!-- These are required for USB HID access (hw wallets). --> + <!-- see https://github.com/Electron-Cash/Electron-Cash/commit/5abec73eee0cdeb725e3c5a989621ec4ccfb92a0 --> + <key>com.apple.security.cs.allow-dyld-environment-variables</key> + <true/> + <key>com.apple.security.cs.allow-jit</key> + <true/> +</dict> +</plist> DIR diff --git a/contrib/osx/make_osx b/contrib/osx/make_osx t@@ -5,11 +5,12 @@ PYTHON_VERSION=3.7.6 BUILDDIR=/tmp/electrum-build PACKAGE=Electrum GIT_REPO=https://github.com/spesmilo/electrum -LIBSECP_VERSION="b408c6a8b287003d1ade5709e6f7bc3c7f1d5be7" export GCC_STRIP_BINARIES="1" -. $(dirname "$0")/base.sh + +. $(dirname "$0")/../build_tools_util.sh + CONTRIB_OSX="$(dirname "$(realpath "$0")")" CONTRIB="$CONTRIB_OSX/.." t@@ -24,26 +25,46 @@ which brew > /dev/null 2>&1 || fail "Please install brew from https://brew.sh/ t which xcodebuild > /dev/null 2>&1 || fail "Please install Xcode and xcode command line tools to continue" # Code Signing: See https://developer.apple.com/library/archive/documentation/Security/Conceptual/CodeSigningGuide/Procedures/Procedures.html -APP_SIGN="" -if [ -n "$1" ]; then +if [ -n "$CODESIGN_CERT" ]; then # Test the identity is valid for signing by doing this hack. There is no other way to do this. cp -f /bin/ls ./CODESIGN_TEST - codesign -s "$1" --dryrun -f ./CODESIGN_TEST > /dev/null 2>&1 + codesign -s "$CODESIGN_CERT" --dryrun -f ./CODESIGN_TEST > /dev/null 2>&1 res=$? rm -f ./CODESIGN_TEST if ((res)); then - fail "Code signing identity \"$1\" appears to be invalid." + fail "Code signing identity \"$CODESIGN_CERT\" appears to be invalid." fi unset res - APP_SIGN="$1" - info "Code signing enabled using identity \"$APP_SIGN\"" + info "Code signing enabled using identity \"$CODESIGN_CERT\"" else - warn "Code signing DISABLED. Specify a valid macOS Developer identity installed on the system as the first argument to this script to enable signing." + warn "Code signing DISABLED. Specify a valid macOS Developer identity installed on the system to enable signing." fi + +function DoCodeSignMaybe { # ARGS: infoName fileOrDirName + infoName="$1" + file="$2" + deep="" + if [ -z "$CODESIGN_CERT" ]; then + # no cert -> we won't codesign + return + fi + if [ -d "$file" ]; then + deep="--deep" + fi + if [ -z "$infoName" ] || [ -z "$file" ] || [ ! -e "$file" ]; then + fail "Argument error to internal function DoCodeSignMaybe()" + fi + hardened_arg="--entitlements=${CONTRIB_OSX}/entitlements.plist -o runtime" + + info "Code signing ${infoName}..." + codesign -f -v $deep -s "$CODESIGN_CERT" $hardened_arg "$file" || fail "Could not code sign ${infoName}" +} + + info "Installing Python $PYTHON_VERSION" export PATH="~/.pyenv/bin:~/.pyenv/shims:~/Library/Python/3.7/bin:$PATH" -if [ -d "~/.pyenv" ]; then +if [ -d "${HOME}/.pyenv" ]; then pyenv update else curl -L https://raw.githubusercontent.com/pyenv/pyenv-installer/master/bin/pyenv-installer | bash > /dev/null 2>&1 t@@ -109,7 +130,7 @@ rm -fr build # prefer building using xcode ourselves. otherwise fallback to prebuilt binary xcodebuild || cp -r prebuilt_qr build || fail "Could not build CalinsQRReader" popd -DoCodeSignMaybe "CalinsQRReader.app" "${d}/build/Release/CalinsQRReader.app" "$APP_SIGN" # If APP_SIGN is empty will be a noop +DoCodeSignMaybe "CalinsQRReader.app" "${d}/build/Release/CalinsQRReader.app" info "Installing requirements..." t@@ -131,7 +152,7 @@ for d in ~/Library/Python/ ~/.pyenv .; do done info "Building binary" -APP_SIGN="$APP_SIGN" pyinstaller --noconfirm --ascii --clean --name $VERSION contrib/osx/osx.spec || fail "Could not build binary" +APP_SIGN="$CODESIGN_CERT" pyinstaller --noconfirm --ascii --clean --name $VERSION contrib/osx/osx.spec || fail "Could not build binary" info "Adding bitcoin URI types to Info.plist" plutil -insert 'CFBundleURLTypes' \ t@@ -139,14 +160,23 @@ plutil -insert 'CFBundleURLTypes' \ -- dist/$PACKAGE.app/Contents/Info.plist \ || fail "Could not add keys to Info.plist. Make sure the program 'plutil' exists and is installed." -DoCodeSignMaybe "app bundle" "dist/${PACKAGE}.app" "$APP_SIGN" # If APP_SIGN is empty will be a noop +DoCodeSignMaybe "app bundle" "dist/${PACKAGE}.app" + +if [ ! -z "$CODESIGN_CERT" ]; then + if [ ! -z "$APPLE_ID_USER" ]; then + info "Notarizing .app with Apple's central server..." + "${CONTRIB_OSX}/notarize_app.sh" "dist/${PACKAGE}.app" || fail "Could not notarize binary." + else + warn "AppleID details not set! Skipping Apple notarization." + fi +fi info "Creating .DMG" hdiutil create -fs HFS+ -volname $PACKAGE -srcfolder dist/$PACKAGE.app dist/electrum-$VERSION.dmg || fail "Could not create .DMG" -DoCodeSignMaybe ".DMG" "dist/electrum-${VERSION}.dmg" "$APP_SIGN" # If APP_SIGN is empty will be a noop +DoCodeSignMaybe ".DMG" "dist/electrum-${VERSION}.dmg" -if [ -z "$APP_SIGN" ]; then +if [ -z "$CODESIGN_CERT" ]; then warn "App was built successfully but was not code signed. Users may get security warnings from macOS." - warn "Specify a valid code signing identity as the first argument to this script to enable code signing." + warn "Specify a valid code signing identity to enable code signing." fi DIR diff --git a/contrib/osx/notarize_app.sh b/contrib/osx/notarize_app.sh t@@ -0,0 +1,77 @@ +#!/usr/bin/env bash +# from https://github.com/metabrainz/picard/blob/e1354632d2db305b7a7624282701d34d73afa225/scripts/package/macos-notarize-app.sh + + +if [ -z "$1" ]; then + echo "Specify app bundle as first parameter" + exit 1 +fi + +if [ -z "$APPLE_ID_USER" ] || [ -z "$APPLE_ID_PASSWORD" ]; then + echo "You need to set your Apple ID credentials with \$APPLE_ID_USER and \$APPLE_ID_PASSWORD." + exit 1 +fi + +APP_BUNDLE=$(basename "$1") +APP_BUNDLE_DIR=$(dirname "$1") + +cd "$APP_BUNDLE_DIR" || exit 1 + +# Package app for submission +echo "Generating ZIP archive ${APP_BUNDLE}.zip..." +ditto -c -k --rsrc --keepParent "$APP_BUNDLE" "${APP_BUNDLE}.zip" + +# Submit for notarization +echo "Submitting $APP_BUNDLE for notarization..." +RESULT=$(xcrun altool --notarize-app --type osx \ + --file "${APP_BUNDLE}.zip" \ + --primary-bundle-id org.electrum.electrum \ + --username $APPLE_ID_USER \ + --password @env:APPLE_ID_PASSWORD \ + --output-format xml) + +if [ $? -ne 0 ]; then + echo "Submitting $APP_BUNDLE failed:" + echo "$RESULT" + exit 1 +fi + +REQUEST_UUID=$(echo "$RESULT" | xpath \ + "//key[normalize-space(text()) = 'RequestUUID']/following-sibling::string[1]/text()" 2> /dev/null) + +if [ -z "$REQUEST_UUID" ]; then + echo "Submitting $APP_BUNDLE failed:" + echo "$RESULT" + exit 1 +fi + +echo "$(echo "$RESULT" | xpath \ + "//key[normalize-space(text()) = 'success-message']/following-sibling::string[1]/text()" 2> /dev/null)" + +# Poll for notarization status +echo "Submitted notarization request $REQUEST_UUID, waiting for response..." +sleep 60 +while : +do + RESULT=$(xcrun altool --notarization-info "$REQUEST_UUID" \ + --username "$APPLE_ID_USER" \ + --password @env:APPLE_ID_PASSWORD \ + --output-format xml) + STATUS=$(echo "$RESULT" | xpath \ + "//key[normalize-space(text()) = 'Status']/following-sibling::string[1]/text()" 2> /dev/null) + + if [ "$STATUS" = "success" ]; then + echo "Notarization of $APP_BUNDLE succeeded!" + break + elif [ "$STATUS" = "in progress" ]; then + echo "Notarization in progress..." + sleep 20 + else + echo "Notarization of $APP_BUNDLE failed:" + echo "$RESULT" + exit 1 + fi +done + +# Staple the notary ticket +xcrun stapler staple "$APP_BUNDLE"