Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 74 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: |
Expand Down
104 changes: 93 additions & 11 deletions Installer/build-pkg.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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 ""

Expand All @@ -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 ""

Expand Down Expand Up @@ -131,11 +189,30 @@ cat > "$PKG_DIR/Distribution.xml" << 'EOF'
</installer-gui-script>
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 ""
Expand All @@ -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 /"
Expand Down
137 changes: 137 additions & 0 deletions docs/APPLE_SIGNING.md
Original file line number Diff line number Diff line change
@@ -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)