diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9db0f2c..5a3e1fb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -51,15 +51,87 @@ jobs: mkdir -p .build/apple/Products/Release cp .build/release/rastertoepiloz .build/apple/Products/Release/ + # Apple Code Signing (only if secrets are configured) + - name: Import Apple Certificates + if: ${{ secrets.APPLE_CERTIFICATE_P12 != '' }} + env: + CERTIFICATE_P12: ${{ secrets.APPLE_CERTIFICATE_P12 }} + CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} + run: | + # Create temporary keychain + KEYCHAIN_PATH=$RUNNER_TEMP/build.keychain + KEYCHAIN_PASSWORD=$(openssl rand -base64 32) + + # Decode certificate + echo "$CERTIFICATE_P12" | base64 --decode > $RUNNER_TEMP/certificate.p12 + + # Create and configure keychain + security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + security set-keychain-settings -lut 21600 $KEYCHAIN_PATH + security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + + # Import certificate + security import $RUNNER_TEMP/certificate.p12 -k $KEYCHAIN_PATH \ + -P "$CERTIFICATE_PASSWORD" -T /usr/bin/codesign -T /usr/bin/productsign + + # Set key partition list + security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + + # Add to search list + security list-keychains -d user -s $KEYCHAIN_PATH login.keychain + + echo "KEYCHAIN_PATH=$KEYCHAIN_PATH" >> $GITHUB_ENV + echo "Apple certificates imported successfully" + - name: Build Installer Package + env: + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} run: | VERSION=${{ steps.version.outputs.version }} - ./Installer/build-pkg.sh - # The script creates EpilogDriver-1.0.0.pkg, rename to match version + + # Build with signing if certificates are available + if [ -n "${{ secrets.APPLE_CERTIFICATE_P12 }}" ]; then + echo "Building SIGNED package..." + ./Installer/build-pkg.sh --sign + else + echo "Building unsigned package (no Apple certificates configured)" + ./Installer/build-pkg.sh + fi + + # Rename to match version if needed if [ -f ".build/pkg/EpilogDriver-1.0.0.pkg" ]; then mv .build/pkg/EpilogDriver-1.0.0.pkg .build/pkg/EpilogDriver-${VERSION}.pkg fi + - name: Notarize Package + if: ${{ secrets.APPLE_CERTIFICATE_P12 != '' }} + env: + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + APPLE_APP_PASSWORD: ${{ secrets.APPLE_APP_PASSWORD }} + run: | + VERSION=${{ steps.version.outputs.version }} + PKG_PATH=".build/pkg/EpilogDriver-${VERSION}.pkg" + + echo "Submitting package for notarization..." + xcrun notarytool submit "$PKG_PATH" \ + --apple-id "$APPLE_ID" \ + --team-id "$APPLE_TEAM_ID" \ + --password "$APPLE_APP_PASSWORD" \ + --wait + + echo "Stapling notarization ticket..." + xcrun stapler staple "$PKG_PATH" + + echo "Package notarized and stapled successfully" + + - name: Cleanup Keychain + if: always() + run: | + if [ -n "$KEYCHAIN_PATH" ]; then + security delete-keychain $KEYCHAIN_PATH 2>/dev/null || true + fi + - name: Create Release Notes id: release_notes run: | diff --git a/Installer/build-pkg.sh b/Installer/build-pkg.sh index 71b6c86..4b2252f 100755 --- a/Installer/build-pkg.sh +++ b/Installer/build-pkg.sh @@ -5,6 +5,15 @@ # This script creates a proper macOS installer package (.pkg) with # a nice GUI installer experience. # +# Usage: +# ./build-pkg.sh # Build unsigned package +# ./build-pkg.sh --sign # Build signed package (requires Apple Developer certs) +# +# Environment variables for signing: +# APPLE_TEAM_ID - Your Apple Developer Team ID +# SIGNING_IDENTITY_APP - Developer ID Application identity (optional, auto-detected) +# SIGNING_IDENTITY_PKG - Developer ID Installer identity (optional, auto-detected) +# set -e @@ -25,7 +34,34 @@ IDENTIFIER="com.epilog.driver" FILTER_DIR="/Library/Printers/Epilog/Filters" PPD_DIR="/Library/Printers/PPDs/Contents/Resources" +# Parse command line arguments +SIGN_PACKAGE=false +for arg in "$@"; do + case $arg in + --sign) + SIGN_PACKAGE=true + shift + ;; + esac +done + +# Set up signing identities +if [ "$SIGN_PACKAGE" = true ]; then + # Use provided identity or find one automatically + if [ -z "$SIGNING_IDENTITY_APP" ]; then + SIGNING_IDENTITY_APP="Developer ID Application" + fi + if [ -z "$SIGNING_IDENTITY_PKG" ]; then + SIGNING_IDENTITY_PKG="Developer ID Installer" + fi +fi + echo "=== Building Epilog Zing Driver Installer ===" +if [ "$SIGN_PACKAGE" = true ]; then + echo "Mode: SIGNED (Apple Developer ID)" +else + echo "Mode: Unsigned" +fi echo "" # Step 1: Build release binaries @@ -67,6 +103,16 @@ cp "$SCRIPT_DIR/Uninstall Epilog Driver.command" "$STAGING_DIR/Library/Printers/ chmod 755 "$STAGING_DIR$FILTER_DIR/rastertoepiloz" chmod 644 "$STAGING_DIR$PPD_DIR/"*.ppd chmod 755 "$STAGING_DIR/Library/Printers/Epilog/Uninstall Epilog Driver.command" + +# Sign the binary if signing is enabled +if [ "$SIGN_PACKAGE" = true ]; then + echo " Signing binary with Developer ID..." + codesign --force --options runtime \ + --sign "$SIGNING_IDENTITY_APP" \ + --timestamp \ + "$STAGING_DIR$FILTER_DIR/rastertoepiloz" + echo " Binary signed." +fi echo " Done." echo "" @@ -85,12 +131,24 @@ echo "Step 4: Creating component package..." rm -rf "$PKG_DIR" mkdir -p "$PKG_DIR" -pkgbuild \ - --root "$STAGING_DIR" \ - --identifier "$IDENTIFIER" \ - --version "$VERSION" \ - --scripts "$SCRIPT_DIR" \ - "$PKG_DIR/$PRODUCT_NAME.pkg" +if [ "$SIGN_PACKAGE" = true ]; then + pkgbuild \ + --root "$STAGING_DIR" \ + --identifier "$IDENTIFIER" \ + --version "$VERSION" \ + --scripts "$SCRIPT_DIR" \ + --sign "$SIGNING_IDENTITY_PKG" \ + --timestamp \ + "$PKG_DIR/$PRODUCT_NAME.pkg" + echo " Component package signed." +else + pkgbuild \ + --root "$STAGING_DIR" \ + --identifier "$IDENTIFIER" \ + --version "$VERSION" \ + --scripts "$SCRIPT_DIR" \ + "$PKG_DIR/$PRODUCT_NAME.pkg" +fi echo " Done." echo "" @@ -131,11 +189,30 @@ cat > "$PKG_DIR/Distribution.xml" << 'EOF' EOF -productbuild \ - --distribution "$PKG_DIR/Distribution.xml" \ - --resources "$RESOURCES_DIR" \ - --package-path "$PKG_DIR" \ - "$PKG_DIR/$PRODUCT_NAME-$VERSION.pkg" +if [ "$SIGN_PACKAGE" = true ]; then + # Create unsigned product first, then sign it + productbuild \ + --distribution "$PKG_DIR/Distribution.xml" \ + --resources "$RESOURCES_DIR" \ + --package-path "$PKG_DIR" \ + "$PKG_DIR/$PRODUCT_NAME-$VERSION-unsigned.pkg" + + # Sign the final product + productsign \ + --sign "$SIGNING_IDENTITY_PKG" \ + --timestamp \ + "$PKG_DIR/$PRODUCT_NAME-$VERSION-unsigned.pkg" \ + "$PKG_DIR/$PRODUCT_NAME-$VERSION.pkg" + + rm -f "$PKG_DIR/$PRODUCT_NAME-$VERSION-unsigned.pkg" + echo " Product archive signed." +else + productbuild \ + --distribution "$PKG_DIR/Distribution.xml" \ + --resources "$RESOURCES_DIR" \ + --package-path "$PKG_DIR" \ + "$PKG_DIR/$PRODUCT_NAME-$VERSION.pkg" +fi echo " Done." echo "" @@ -148,6 +225,11 @@ echo "=== Build Complete ===" echo "" echo "Installer package created:" echo " $PKG_DIR/$PRODUCT_NAME-$VERSION.pkg" +if [ "$SIGN_PACKAGE" = true ]; then + echo "" + echo "Package is SIGNED with Apple Developer ID." + echo "Next step: Notarize with 'xcrun notarytool submit'" +fi echo "" echo "To install, double-click the package or run:" echo " sudo installer -pkg '$PKG_DIR/$PRODUCT_NAME-$VERSION.pkg' -target /" diff --git a/docs/APPLE_SIGNING.md b/docs/APPLE_SIGNING.md new file mode 100644 index 0000000..321716d --- /dev/null +++ b/docs/APPLE_SIGNING.md @@ -0,0 +1,137 @@ +# Apple Code Signing & Notarization + +This document explains how to enable Apple Developer ID signing for the Epilog Driver installer package. + +## Overview + +When enabled, the release workflow will: +1. Sign the `rastertoepiloz` binary with Developer ID Application certificate +2. Sign the installer package with Developer ID Installer certificate +3. Submit the package to Apple for notarization +4. Staple the notarization ticket to the package + +This eliminates Gatekeeper warnings when users install the driver. + +## Code Changes + +### `Installer/build-pkg.sh` +- Added `--sign` flag to enable code signing mode +- Signs binary with hardened runtime (`codesign --options runtime`) +- Signs component package (`pkgbuild --sign`) +- Signs final product archive (`productsign`) + +### `.github/workflows/release.yml` +- Imports certificates from GitHub secrets into a temporary keychain +- Conditionally signs based on whether `APPLE_CERTIFICATE_P12` secret exists +- Submits to Apple notarization service (`xcrun notarytool`) +- Staples notarization ticket (`xcrun stapler`) +- Cleans up temporary keychain after job completes + +## Current Behavior + +- **Without secrets configured**: Releases build unsigned packages (works as before) +- **With secrets configured**: Releases are automatically signed and notarized + +## Setup Instructions + +### Step 1: Create Certificates + +1. Log in to https://developer.apple.com/account +2. Go to **Certificates, Identifiers & Profiles** +3. Click **+** to create a new certificate +4. Select **Developer ID Application** → Continue +5. Upload a Certificate Signing Request (CSR): + - Open Keychain Access + - Certificate Assistant → Request a Certificate from a Certificate Authority + - Save to disk +6. Download the certificate and double-click to install +7. Repeat steps 3-6 for **Developer ID Installer** certificate + +### Step 2: Export Certificates + +1. Open **Keychain Access** +2. Find your "Developer ID Application" certificate +3. Right-click → Export → Save as `.p12` with a strong password +4. Repeat for "Developer ID Installer" certificate +5. Combine and encode for GitHub: + +```bash +# Combine certificates (if exported separately) +cat "Developer ID Application.p12" "Developer ID Installer.p12" > combined.p12 + +# Or export both at once by selecting both in Keychain Access + +# Base64 encode for GitHub +base64 -i combined.p12 | pbcopy +# The encoded string is now in your clipboard +``` + +### Step 3: Create App-Specific Password + +1. Go to https://appleid.apple.com/account/manage +2. Sign in with your Apple ID +3. Navigate to **App-Specific Passwords** +4. Click **Generate** +5. Name it "EpilogDriver Notarization" +6. Save the generated password securely + +### Step 4: Add GitHub Secrets + +Go to your repository: **Settings** → **Secrets and variables** → **Actions** → **New repository secret** + +Add these secrets: + +| Secret Name | Value | +|-------------|-------| +| `APPLE_CERTIFICATE_P12` | Base64-encoded .p12 file from Step 2 | +| `APPLE_CERTIFICATE_PASSWORD` | Password used when exporting .p12 | +| `APPLE_ID` | Your Apple ID email address | +| `APPLE_TEAM_ID` | Your Team ID (found at developer.apple.com) | +| `APPLE_APP_PASSWORD` | App-specific password from Step 3 | + +### Step 5: Merge and Test + +```bash +# Merge the signing branch into master +git checkout master +git merge feature/apple-signing +git push origin master + +# Create a test release +git tag v1.3.0-beta +git push origin v1.3.0-beta +``` + +Monitor the GitHub Actions workflow to verify signing and notarization succeed. + +## Troubleshooting + +### "No identity found" +- Ensure both Developer ID Application and Installer certificates are in the .p12 +- Verify the .p12 password is correct in `APPLE_CERTIFICATE_PASSWORD` + +### Notarization fails +- Check that `APPLE_ID` and `APPLE_APP_PASSWORD` are correct +- Ensure the Apple ID has accepted the latest developer agreements +- Verify `APPLE_TEAM_ID` matches your developer account + +### "The signature is invalid" +- Make sure hardened runtime is enabled (`--options runtime`) +- Verify the binary doesn't use any disallowed entitlements + +## Local Signing (Optional) + +You can also sign locally for testing: + +```bash +# Build signed package locally +./Installer/build-pkg.sh --sign + +# The script will use certificates from your login keychain +``` + +## References + +- [Apple Developer ID](https://developer.apple.com/developer-id/) +- [Notarizing macOS Software](https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution) +- [Creating Developer ID Certificates](https://developer.apple.com/help/account/create-certificates/create-developer-id-certificates)