mirror of
https://github.com/praktimarc/kst4contest.git
synced 2026-08-23 18:47:34 +02:00
Sign and notarize macOS builds
jpackage cannot produce a distributable macOS bundle on its own. It ad-hoc signs the embedded runtime and then re-runs codesign on the same files without --force, which codesign rejects; and "--type dmg --app-image" re-signs the app it is handed, replacing a Developer ID signature with an ad-hoc one. So the build now creates an unsigned app-image, signs it from the inside out, and wraps it with hdiutil. Apple's notary service also unpacks JARs and checks the native libraries inside them, which sqlite-jdbc ships for both architectures. Those are signed before the bundle is sealed, since rewriting a JAR afterwards would invalidate the seal. A preflight check verifies Apple's two criteria locally, so a missed binary costs seconds rather than a round trip to the notary service. Two long-standing defects surfaced while testing and are fixed here: the bundle identifier defaulted to the main class's package name (kst4contest.view instead of de.x08.KST4Contest), and every release reported version 1.0 in Finder because --app-version was never passed. Neither affects existing users: the app keeps its settings in ~/.praktiKST, independent of the bundle ID. Both workflows call the same script the local Mac uses, so the two cannot drift apart. Signing needs a keychain that can answer a UI prompt, which a runner cannot, so ci-import-cert.sh creates a throwaway keychain whose password is generated per job and discarded with it. Notarization goes through an App Store Connect API key and needs no keychain at all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WYcmHra3YndA1ahkHeNdJ2
This commit is contained in:
@@ -7,6 +7,7 @@ on:
|
||||
paths:
|
||||
- "src/**"
|
||||
- "packaging/icons/**"
|
||||
- "packaging/macos/**"
|
||||
- "pom.xml"
|
||||
- "mvnw"
|
||||
- "mvnw.cmd"
|
||||
@@ -634,34 +635,38 @@ jobs:
|
||||
- name: Ensure mvnw is executable
|
||||
run: chmod +x mvnw
|
||||
|
||||
- name: Build JAR and copy runtime dependencies
|
||||
run: |
|
||||
./mvnw -B -DskipTests package dependency:copy-dependencies -DincludeScope=runtime -DoutputDirectory=target/dist-libs
|
||||
cp "$(ls -t target/praktiKST-*.jar | head -n 1)" target/dist-libs/app.jar
|
||||
|
||||
- name: Build macOS DMG with jpackage
|
||||
run: |
|
||||
mkdir -p dist
|
||||
ADD_MODULES="$(java packaging/AddModules.java)"
|
||||
jpackage \
|
||||
--type dmg \
|
||||
--name KST4Contest \
|
||||
--icon packaging/icons/kst4contest.icns \
|
||||
--input target/dist-libs \
|
||||
--main-jar app.jar \
|
||||
--main-class kst4contest.view.Kst4ContestApplication \
|
||||
--module-path target/dist-libs \
|
||||
--add-modules "$ADD_MODULES" \
|
||||
--dest dist
|
||||
|
||||
- name: Import signing certificate
|
||||
env:
|
||||
MACOSX_DEPLOYMENT_TARGET: "13.0"
|
||||
MACOS_CERT_P12: ${{ secrets.MACOS_CERT_P12 }}
|
||||
MACOS_CERT_PASSWORD: ${{ secrets.MACOS_CERT_PASSWORD }}
|
||||
run: ./packaging/macos/ci-import-cert.sh
|
||||
|
||||
# Builds the jar, signs the app bundle and every native library inside it,
|
||||
# wraps it into a DMG and has Apple notarize the result. Same script the
|
||||
# local Mac uses, so the two cannot drift apart.
|
||||
- name: Build signed and notarized DMG
|
||||
env:
|
||||
MACOS_NOTARY_KEY: ${{ secrets.MACOS_NOTARY_KEY }}
|
||||
NOTARY_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }}
|
||||
NOTARY_ISSUER: ${{ secrets.MACOS_NOTARY_ISSUER }}
|
||||
run: |
|
||||
printf '%s' "$MACOS_NOTARY_KEY" | base64 --decode > "$RUNNER_TEMP/notary.p8"
|
||||
export NOTARY_KEY="$RUNNER_TEMP/notary.p8"
|
||||
./packaging/macos/build-signed-dmg.sh
|
||||
|
||||
- name: Remove signing credentials
|
||||
if: always()
|
||||
run: |
|
||||
rm -f "$RUNNER_TEMP/notary.p8"
|
||||
if [ -n "${SIGNING_KEYCHAIN:-}" ]; then
|
||||
security delete-keychain "$SIGNING_KEYCHAIN" || true
|
||||
fi
|
||||
|
||||
- name: Rename DMG artifact
|
||||
run: |
|
||||
DMG=$(ls dist/*.dmg | head -n 1)
|
||||
if [ -z "$DMG" ]; then
|
||||
echo "No DMG produced by jpackage" && exit 1
|
||||
echo "No DMG produced by the build" && exit 1
|
||||
fi
|
||||
mv "$DMG" "dist/${ASSET_BASENAME}-macos-${ARCH}.dmg"
|
||||
|
||||
|
||||
@@ -522,35 +522,39 @@ jobs:
|
||||
- name: Ensure mvnw is executable
|
||||
run: chmod +x mvnw
|
||||
|
||||
- name: Build JAR and copy runtime dependencies
|
||||
run: |
|
||||
./mvnw -B -DskipTests package dependency:copy-dependencies -DincludeScope=runtime -DoutputDirectory=target/dist-libs
|
||||
cp "$(ls -t target/praktiKST-*.jar | head -n 1)" target/dist-libs/app.jar
|
||||
|
||||
- name: Build macOS DMG with jpackage
|
||||
run: |
|
||||
mkdir -p dist
|
||||
ADD_MODULES="$(java packaging/AddModules.java)"
|
||||
jpackage \
|
||||
--type dmg \
|
||||
--name KST4Contest \
|
||||
--icon packaging/icons/kst4contest.icns \
|
||||
--input target/dist-libs \
|
||||
--main-jar app.jar \
|
||||
--main-class kst4contest.view.Kst4ContestApplication \
|
||||
--module-path target/dist-libs \
|
||||
--add-modules "$ADD_MODULES" \
|
||||
--dest dist
|
||||
|
||||
- name: Import signing certificate
|
||||
env:
|
||||
MACOSX_DEPLOYMENT_TARGET: "13.0"
|
||||
MACOS_CERT_P12: ${{ secrets.MACOS_CERT_P12 }}
|
||||
MACOS_CERT_PASSWORD: ${{ secrets.MACOS_CERT_PASSWORD }}
|
||||
run: ./packaging/macos/ci-import-cert.sh
|
||||
|
||||
# Builds the jar, signs the app bundle and every native library inside it,
|
||||
# wraps it into a DMG and has Apple notarize the result. Same script the
|
||||
# local Mac uses, so the two cannot drift apart.
|
||||
- name: Build signed and notarized DMG
|
||||
env:
|
||||
MACOS_NOTARY_KEY: ${{ secrets.MACOS_NOTARY_KEY }}
|
||||
NOTARY_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }}
|
||||
NOTARY_ISSUER: ${{ secrets.MACOS_NOTARY_ISSUER }}
|
||||
run: |
|
||||
printf '%s' "$MACOS_NOTARY_KEY" | base64 --decode > "$RUNNER_TEMP/notary.p8"
|
||||
export NOTARY_KEY="$RUNNER_TEMP/notary.p8"
|
||||
./packaging/macos/build-signed-dmg.sh
|
||||
|
||||
- name: Remove signing credentials
|
||||
if: always()
|
||||
run: |
|
||||
rm -f "$RUNNER_TEMP/notary.p8"
|
||||
if [ -n "${SIGNING_KEYCHAIN:-}" ]; then
|
||||
security delete-keychain "$SIGNING_KEYCHAIN" || true
|
||||
fi
|
||||
|
||||
- name: Rename DMG artifact
|
||||
run: |
|
||||
ARCH=$(uname -m)
|
||||
DMG=$(ls dist/*.dmg | head -n 1)
|
||||
if [ -z "$DMG" ]; then
|
||||
echo "No DMG produced by jpackage" && exit 1
|
||||
echo "No DMG produced by the build" && exit 1
|
||||
fi
|
||||
mv "$DMG" "dist/KST4Contest-${{ github.ref_name }}-macos-${ARCH}.dmg"
|
||||
|
||||
|
||||
@@ -44,3 +44,6 @@ website/_site/
|
||||
|
||||
# Local secrets for act testing
|
||||
.secrets
|
||||
|
||||
# Apple notarization private keys - never commit these
|
||||
*.p8
|
||||
|
||||
Executable
+265
@@ -0,0 +1,265 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Local signed (and optionally notarized) macOS build.
|
||||
#
|
||||
# jpackage cannot sign the app itself: it ad-hoc signs the embedded runtime and
|
||||
# then re-runs codesign on the same files without --force, which codesign
|
||||
# rejects with "is already signed". So this builds an unsigned app-image, signs
|
||||
# it from the inside out ourselves, and only then wraps it into a DMG.
|
||||
#
|
||||
# Required:
|
||||
# SIGNING_IDENTITY The name part of the Developer ID Application certificate,
|
||||
# without the "Developer ID Application: " prefix. Example:
|
||||
# SIGNING_IDENTITY="Philipp Wagner (ABCDE12345)"
|
||||
# List available ones with:
|
||||
# security find-identity -v -p codesigning
|
||||
#
|
||||
# Optional:
|
||||
# Notarization, either as three separate values (what CI uses)...
|
||||
# NOTARY_KEY Path to the App Store Connect .p8 private key
|
||||
# NOTARY_KEY_ID The key's ID, also part of the .p8 filename
|
||||
# NOTARY_ISSUER The issuer UUID, shown above the key list in the portal
|
||||
# ...or as a keychain profile previously created with
|
||||
# NOTARY_PROFILE xcrun notarytool store-credentials <name>
|
||||
#
|
||||
# With neither, the build is signed but not notarized -- enough to test
|
||||
# locally, not enough to distribute.
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/../.."
|
||||
REPO_ROOT="$PWD"
|
||||
|
||||
BUNDLE_ID="de.x08.KST4Contest"
|
||||
ENTITLEMENTS="packaging/macos/kst4contest.entitlements"
|
||||
|
||||
if [ -z "${SIGNING_IDENTITY:-}" ]; then
|
||||
echo "SIGNING_IDENTITY is not set. Available signing identities:" >&2
|
||||
security find-identity -v -p codesigning >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
FULL_IDENTITY="Developer ID Application: $SIGNING_IDENTITY"
|
||||
|
||||
# notarytool takes either an API key triple or a stored keychain profile. The
|
||||
# triple needs no keychain at all, which is why CI uses it.
|
||||
NOTARY_ARGS=()
|
||||
if [ -n "${NOTARY_KEY:-}" ] && [ -n "${NOTARY_KEY_ID:-}" ] && [ -n "${NOTARY_ISSUER:-}" ]; then
|
||||
NOTARY_ARGS=(--key "$NOTARY_KEY" --key-id "$NOTARY_KEY_ID" --issuer "$NOTARY_ISSUER")
|
||||
elif [ -n "${NOTARY_PROFILE:-}" ]; then
|
||||
NOTARY_ARGS=(--keychain-profile "$NOTARY_PROFILE")
|
||||
fi
|
||||
|
||||
echo "==> Building JAR and collecting runtime dependencies"
|
||||
chmod +x mvnw
|
||||
./mvnw -B -DskipTests package \
|
||||
dependency:copy-dependencies -DincludeScope=runtime -DoutputDirectory=target/dist-libs
|
||||
JAR="$(ls -t target/praktiKST-*.jar | head -n 1)"
|
||||
cp "$JAR" target/dist-libs/app.jar
|
||||
|
||||
# jpackage only accepts a numeric major[.minor[.patch]] as the macOS bundle
|
||||
# version, so a Maven qualifier like "-nightly" has to be trimmed off.
|
||||
POM_VERSION="${JAR##*/praktiKST-}"
|
||||
POM_VERSION="${POM_VERSION%.jar}"
|
||||
APP_VERSION="$(printf '%s' "$POM_VERSION" | sed -e 's/[^0-9.].*$//' -e 's/\.*$//')"
|
||||
[ -n "$APP_VERSION" ] || { echo "Could not derive app version from $JAR" >&2; exit 1; }
|
||||
echo "==> Version: $POM_VERSION -> bundle version $APP_VERSION"
|
||||
|
||||
echo "==> Step 1/4: jpackage app-image (unsigned)"
|
||||
rm -rf dist
|
||||
mkdir -p dist
|
||||
ADD_MODULES="$(java packaging/AddModules.java)"
|
||||
|
||||
MACOSX_DEPLOYMENT_TARGET="13.0" jpackage \
|
||||
--type app-image \
|
||||
--name KST4Contest \
|
||||
--app-version "$APP_VERSION" \
|
||||
--icon packaging/icons/kst4contest.icns \
|
||||
--input target/dist-libs \
|
||||
--main-jar app.jar \
|
||||
--main-class kst4contest.view.Kst4ContestApplication \
|
||||
--module-path target/dist-libs \
|
||||
--add-modules "$ADD_MODULES" \
|
||||
--mac-package-identifier "$BUNDLE_ID" \
|
||||
--mac-package-name KST4Contest \
|
||||
--dest dist/appimage
|
||||
|
||||
APP="dist/appimage/KST4Contest.app"
|
||||
[ -d "$APP" ] || { echo "jpackage produced no app image" >&2; exit 1; }
|
||||
|
||||
echo "==> Step 2/4: signing bundle contents (this takes a few minutes)"
|
||||
|
||||
# Apple's notary service unpacks JARs and checks the native libraries inside
|
||||
# them. sqlite-jdbc ships libsqlitejdbc.dylib for both architectures that way,
|
||||
# and an unsigned binary in there fails the whole submission. So sign those
|
||||
# first: the app bundle's seal covers Contents/app, and rewriting a JAR
|
||||
# afterwards would invalidate it.
|
||||
echo " scanning jars for native libraries"
|
||||
find "$APP/Contents/app" -name '*.jar' -type f | while read -r JARPATH; do
|
||||
# Only unpack jars that can plausibly hold a native library. Note the
|
||||
# plain grep: "grep -q" exits at the first match, which hands unzip a
|
||||
# SIGPIPE, and under "set -o pipefail" that failure becomes the pipeline's
|
||||
# status -- inverting this very test.
|
||||
if ! unzip -l "$JARPATH" | grep -E '\.(dylib|jnilib|so)$' >/dev/null; then
|
||||
continue
|
||||
fi
|
||||
|
||||
JARABS="$(cd "$(dirname "$JARPATH")" && pwd)/$(basename "$JARPATH")"
|
||||
JARTMP="$(mktemp -d)"
|
||||
unzip -q "$JARABS" -d "$JARTMP"
|
||||
|
||||
NATIVES="$(mktemp)"
|
||||
( cd "$JARTMP" && find . -type f \( -name '*.dylib' -o -name '*.jnilib' -o -name '*.so' \) \
|
||||
| while read -r n; do
|
||||
if [ "$(file --mime-type -b "$n")" = "application/x-mach-binary" ]; then
|
||||
printf '%s\n' "${n#./}"
|
||||
fi
|
||||
done ) > "$NATIVES"
|
||||
|
||||
if [ -s "$NATIVES" ]; then
|
||||
echo " $(basename "$JARPATH"): $(wc -l < "$NATIVES" | tr -d ' ') native lib(s)"
|
||||
( cd "$JARTMP" && xargs -I {} codesign --force --timestamp --options runtime \
|
||||
--sign "$FULL_IDENTITY" {} < "$NATIVES" )
|
||||
# Update in place rather than repacking, so the rest of the jar --
|
||||
# manifest, module descriptor, entry order -- stays byte for byte.
|
||||
( cd "$JARTMP" && xargs jar --update --file "$JARABS" < "$NATIVES" )
|
||||
fi
|
||||
|
||||
rm -rf "$JARTMP" "$NATIVES"
|
||||
done
|
||||
|
||||
# Every Mach-O file has to carry its own signature before the enclosing bundle
|
||||
# can be sealed, so collect them first. jpackage leaves them ad-hoc signed,
|
||||
# hence --force on every call.
|
||||
# file(1) pads its output into columns when given several arguments at once,
|
||||
# so ask it one file at a time with -b and get an unambiguous answer.
|
||||
MACHO_LIST="$(mktemp)"
|
||||
find "$APP" -type f -print0 | while IFS= read -r -d '' f; do
|
||||
case "$(file --mime-type -b "$f")" in
|
||||
application/x-mach-binary) printf '%s\n' "$f" ;;
|
||||
esac
|
||||
done > "$MACHO_LIST"
|
||||
|
||||
COUNT="$(wc -l < "$MACHO_LIST" | tr -d ' ')"
|
||||
echo " $COUNT Mach-O files to sign"
|
||||
|
||||
# codesign contacts Apple's timestamp server on every call, so run a handful in
|
||||
# parallel or this takes far longer than it needs to.
|
||||
xargs -P 8 -I {} codesign --force --timestamp --options runtime \
|
||||
--sign "$FULL_IDENTITY" {} < "$MACHO_LIST"
|
||||
rm -f "$MACHO_LIST"
|
||||
|
||||
# The embedded JDK is a bundle in its own right and must be sealed before the
|
||||
# app that contains it.
|
||||
echo " sealing embedded runtime"
|
||||
codesign --force --timestamp --options runtime \
|
||||
--sign "$FULL_IDENTITY" "$APP/Contents/runtime"
|
||||
|
||||
# Entitlements go on the outermost bundle: the hardened runtime derives the
|
||||
# process's entitlements from the main executable's signature.
|
||||
echo " sealing app bundle"
|
||||
codesign --force --timestamp --options runtime \
|
||||
--entitlements "$ENTITLEMENTS" \
|
||||
--sign "$FULL_IDENTITY" "$APP"
|
||||
|
||||
# Apple rejects the whole submission over a single unsigned native library, and
|
||||
# a round trip to the notary service costs minutes. Check its two criteria --
|
||||
# a Developer ID authority and a secure timestamp -- locally first.
|
||||
echo " preflight: verifying native libraries inside jars"
|
||||
PREFLIGHT_ERRORS="$(mktemp)"
|
||||
find "$APP/Contents/app" -name '*.jar' -type f | while read -r JARPATH; do
|
||||
if ! unzip -l "$JARPATH" | grep -E '\.(dylib|jnilib|so)$' >/dev/null; then
|
||||
continue
|
||||
fi
|
||||
CHECKTMP="$(mktemp -d)"
|
||||
unzip -q "$JARPATH" -d "$CHECKTMP"
|
||||
find "$CHECKTMP" -type f \( -name '*.dylib' -o -name '*.jnilib' -o -name '*.so' \) \
|
||||
| while read -r NATIVE; do
|
||||
[ "$(file --mime-type -b "$NATIVE")" = "application/x-mach-binary" ] || continue
|
||||
INFO="$(codesign -dv --verbose=2 "$NATIVE" 2>&1 || true)"
|
||||
LABEL="$(basename "$JARPATH")/${NATIVE#"$CHECKTMP"/}"
|
||||
printf '%s' "$INFO" | grep -q "Authority=Developer ID Application" \
|
||||
|| echo "$LABEL: not signed with a Developer ID certificate" >> "$PREFLIGHT_ERRORS"
|
||||
printf '%s' "$INFO" | grep -q "Timestamp=" \
|
||||
|| echo "$LABEL: signature has no secure timestamp" >> "$PREFLIGHT_ERRORS"
|
||||
done
|
||||
rm -rf "$CHECKTMP"
|
||||
done
|
||||
if [ -s "$PREFLIGHT_ERRORS" ]; then
|
||||
echo "ERROR: these would fail notarization:" >&2
|
||||
sed 's/^/ /' "$PREFLIGHT_ERRORS" >&2
|
||||
rm -f "$PREFLIGHT_ERRORS"
|
||||
exit 1
|
||||
fi
|
||||
rm -f "$PREFLIGHT_ERRORS"
|
||||
echo " preflight ok"
|
||||
|
||||
echo "==> Step 3/4: building the dmg"
|
||||
|
||||
# Not with jpackage: "jpackage --type dmg --app-image" re-signs the app it is
|
||||
# handed, replacing our Developer ID signature with an ad-hoc one and dropping
|
||||
# the hardened runtime flag. hdiutil copies the bundle verbatim instead.
|
||||
DMG="dist/KST4Contest-${APP_VERSION}.dmg"
|
||||
STAGE="$(mktemp -d)"
|
||||
# ditto rather than cp -R: it preserves the extended attributes the code
|
||||
# signature depends on.
|
||||
ditto "$APP" "$STAGE/KST4Contest.app"
|
||||
ln -s /Applications "$STAGE/Applications"
|
||||
|
||||
hdiutil create -volname "KST4Contest" -srcfolder "$STAGE" \
|
||||
-ov -format UDZO -quiet "$DMG"
|
||||
rm -rf "$STAGE"
|
||||
[ -f "$DMG" ] || { echo "hdiutil produced no DMG" >&2; exit 1; }
|
||||
|
||||
# Signing the DMG itself is not what Gatekeeper judges -- that is the .app
|
||||
# inside -- but Apple expects the container to be signed too.
|
||||
codesign --force --timestamp --sign "$FULL_IDENTITY" "$DMG"
|
||||
echo "==> Built $DMG"
|
||||
|
||||
echo "==> Step 4/4: verification"
|
||||
|
||||
if [ ${#NOTARY_ARGS[@]} -gt 0 ]; then
|
||||
echo " submitting for notarization (waits for Apple's verdict)"
|
||||
# Without a timeout a stalled submission would hang a CI job forever.
|
||||
xcrun notarytool submit "$DMG" "${NOTARY_ARGS[@]}" --wait --timeout 30m
|
||||
echo " stapling ticket"
|
||||
xcrun stapler staple "$DMG"
|
||||
else
|
||||
echo " no notarization credentials set, skipping notarization"
|
||||
fi
|
||||
|
||||
# Everything below inspects the app as it actually ships, mounted from the DMG,
|
||||
# rather than the staging copy on disk.
|
||||
MOUNT_POINT="$(mktemp -d)"
|
||||
hdiutil attach "$DMG" -nobrowse -quiet -mountpoint "$MOUNT_POINT"
|
||||
trap 'hdiutil detach "$MOUNT_POINT" -quiet 2>/dev/null || hdiutil detach "$MOUNT_POINT" -force -quiet 2>/dev/null || true' EXIT
|
||||
SHIPPED_APP="$MOUNT_POINT/KST4Contest.app"
|
||||
|
||||
echo "--- codesign --verify on the app inside the DMG ---"
|
||||
codesign --verify --deep --strict --verbose=2 "$SHIPPED_APP"
|
||||
|
||||
echo "--- app identity ---"
|
||||
codesign -dv --verbose=2 "$SHIPPED_APP" 2>&1 | grep -iE "identifier|authority|teamidentifier|flags"
|
||||
|
||||
# An ad-hoc signature here means something along the way re-signed the bundle.
|
||||
if codesign -dv "$SHIPPED_APP" 2>&1 | grep -q "adhoc"; then
|
||||
echo "ERROR: the app inside the DMG is ad-hoc signed, not Developer ID signed" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "--- entitlements as signed ---"
|
||||
codesign -d --entitlements - --xml "$SHIPPED_APP" 2>/dev/null | plutil -convert xml1 -o - - | grep -E "key|true|false"
|
||||
|
||||
echo "--- dmg identity ---"
|
||||
codesign -dv --verbose=2 "$DMG" 2>&1 | grep -iE "authority|teamidentifier" | head -2
|
||||
|
||||
echo "--- spctl assessment ---"
|
||||
# Without notarization this reports "rejected"; that is expected.
|
||||
spctl --assess --type execute --verbose=4 "$SHIPPED_APP" || true
|
||||
|
||||
if [ ${#NOTARY_ARGS[@]} -gt 0 ]; then
|
||||
echo "--- stapler validate ---"
|
||||
xcrun stapler validate "$DMG"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "Done: $REPO_ROOT/$DMG"
|
||||
Executable
+64
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Import the Developer ID certificate into a throwaway keychain on a CI runner.
|
||||
#
|
||||
# A runner cannot answer the keychain's authorization dialog, so the login
|
||||
# keychain is unusable there. This creates a dedicated keychain instead, whose
|
||||
# password is generated here and needed nowhere else -- it is discarded with the
|
||||
# keychain at the end of the job.
|
||||
#
|
||||
# Reads from the environment:
|
||||
# MACOS_CERT_P12 base64 of the exported .p12
|
||||
# MACOS_CERT_PASSWORD the password that .p12 was exported with
|
||||
#
|
||||
# Exports to $GITHUB_ENV:
|
||||
# SIGNING_IDENTITY for packaging/macos/build-signed-dmg.sh
|
||||
# SIGNING_KEYCHAIN so the cleanup step knows what to delete
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
: "${MACOS_CERT_P12:?MACOS_CERT_P12 is not set}"
|
||||
: "${MACOS_CERT_PASSWORD:?MACOS_CERT_PASSWORD is not set}"
|
||||
: "${RUNNER_TEMP:?RUNNER_TEMP is not set}"
|
||||
: "${GITHUB_ENV:?GITHUB_ENV is not set}"
|
||||
|
||||
KEYCHAIN="$RUNNER_TEMP/kst4contest-signing.keychain-db"
|
||||
KEYCHAIN_PASSWORD="$(uuidgen)"
|
||||
CERT="$RUNNER_TEMP/cert.p12"
|
||||
|
||||
printf '%s' "$MACOS_CERT_P12" | base64 --decode > "$CERT"
|
||||
|
||||
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN"
|
||||
# Keychains re-lock after five minutes by default, which would strand a build
|
||||
# halfway through signing.
|
||||
security set-keychain-settings -lut 21600 "$KEYCHAIN"
|
||||
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN"
|
||||
|
||||
security import "$CERT" -k "$KEYCHAIN" -P "$MACOS_CERT_PASSWORD" \
|
||||
-T /usr/bin/codesign -T /usr/bin/security
|
||||
rm -f "$CERT"
|
||||
|
||||
# Lets codesign reach the private key without the UI prompt a runner has no way
|
||||
# of answering.
|
||||
security set-key-partition-list -S apple-tool:,apple:,codesign: \
|
||||
-s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN" >/dev/null
|
||||
|
||||
# codesign searches the keychain list, so the new keychain has to be on it --
|
||||
# added to whatever the runner already had, not in place of it.
|
||||
EXISTING_KEYCHAINS="$(security list-keychains -d user | sed -e 's/^[[:space:]]*"//' -e 's/"$//')"
|
||||
# shellcheck disable=SC2086
|
||||
security list-keychains -d user -s "$KEYCHAIN" $EXISTING_KEYCHAINS
|
||||
|
||||
IDENTITY="$(security find-identity -v -p codesigning "$KEYCHAIN" \
|
||||
| sed -n 's/.*"Developer ID Application: \(.*\)".*/\1/p' | head -n 1)"
|
||||
|
||||
if [ -z "$IDENTITY" ]; then
|
||||
echo "No 'Developer ID Application' identity found in the imported certificate." >&2
|
||||
echo "What the keychain does contain:" >&2
|
||||
security find-identity -v -p codesigning "$KEYCHAIN" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Imported identity: Developer ID Application: $IDENTITY"
|
||||
echo "SIGNING_IDENTITY=$IDENTITY" >> "$GITHUB_ENV"
|
||||
echo "SIGNING_KEYCHAIN=$KEYCHAIN" >> "$GITHUB_ENV"
|
||||
@@ -0,0 +1,21 @@
|
||||
<?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>
|
||||
<!-- The JVM compiles bytecode to machine code at runtime and executes it
|
||||
from memory it allocated itself. Under the hardened runtime all three
|
||||
of these are required or the app is killed on launch. -->
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
<!-- jpackage bundles JavaFX native libraries that are signed with our own
|
||||
identity rather than Apple's, and the JVM dlopen()s them at runtime. -->
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
|
||||
<!-- KST4Contest talks to the ON4KST chat servers. -->
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
Reference in New Issue
Block a user