diff --git a/.env.example b/.env.example index 20bb71e..f320ff7 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,10 @@ +# Development signing (./build.sh or ./build.sh dev) CODESIGN_IDENTITY="Apple Development: Your Name (XXXXXXXXXX)" BUNDLE_ID="com.yourcompany.plc-touch" TEAM_ID="XXXXXXXXXX" + +# Release signing (./build.sh release) +# Requires a "Developer ID Application" certificate from developer.apple.com +DEVELOPER_ID="Developer ID Application: Your Name (XXXXXXXXXX)" +APPLE_ID="your@email.com" +NOTARIZE_PASSWORD="xxxx-xxxx-xxxx-xxxx" # App-specific password from appleid.apple.com diff --git a/.gitignore b/.gitignore index ff2b536..2ec82ec 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ Cargo.lock # Apple signing (contains device UDIDs and developer certificates) embedded.provisionprofile +release.provisionprofile entitlements.plist # Claude Code local settings diff --git a/build.sh b/build.sh index ab817eb..a008325 100755 --- a/build.sh +++ b/build.sh @@ -14,10 +14,33 @@ fi # CODESIGN_IDENTITY="Apple Development: Your Name (XXXXXXXXXX)" # BUNDLE_ID="com.yourcompany.plc-touch" # TEAM_ID="XXXXXXXXXX" -IDENTITY="${CODESIGN_IDENTITY:?Set CODESIGN_IDENTITY in .env or as env var}" +# +# For release builds, also set: +# DEVELOPER_ID="Developer ID Application: Your Name (XXXXXXXXXX)" +# APPLE_ID="your@email.com" +# NOTARIZE_PASSWORD="app-specific-password" BUNDLE_ID="${BUNDLE_ID:-com.example.plc-touch}" TEAM_ID="${TEAM_ID:-XXXXXXXXXX}" +MODE="${1:-dev}" + +case "$MODE" in + dev) + IDENTITY="${CODESIGN_IDENTITY:?Set CODESIGN_IDENTITY in .env}" + ;; + release) + IDENTITY="${DEVELOPER_ID:?Set DEVELOPER_ID in .env for release builds}" + ;; + *) + echo "Usage: ./build.sh [dev|release]" + echo " dev — Development build with provisioning profile (default)" + echo " release — Developer ID build with notarization for distribution" + exit 1 + ;; +esac + +echo "Building plc-touch ($MODE)..." + KEYCHAIN_ACCESS_GROUP="${TEAM_ID}.${BUNDLE_ID}" cargo build --release # Create .app bundle @@ -26,9 +49,11 @@ mkdir -p "$APP/Contents/MacOS" cp target/release/plc-touch "$APP/Contents/MacOS/plc-touch" -# Copy provisioning profile if it exists -if [ -f embedded.provisionprofile ]; then +# Embed provisioning profile +if [ "$MODE" = "dev" ] && [ -f embedded.provisionprofile ]; then cp embedded.provisionprofile "$APP/Contents/embedded.provisionprofile" +elif [ "$MODE" = "release" ] && [ -f release.provisionprofile ]; then + cp release.provisionprofile "$APP/Contents/embedded.provisionprofile" fi cat > "$APP/Contents/Info.plist" << EOF @@ -54,9 +79,11 @@ cat > "$APP/Contents/Info.plist" << EOF EOF -# Generate entitlements from env vars +# Generate entitlements ENTITLEMENTS_FILE=$(mktemp) -cat > "$ENTITLEMENTS_FILE" << EOF +if [ "$MODE" = "release" ]; then + # Developer ID with provisioning profile: includes application-identifier + keychain-access-groups + cat > "$ENTITLEMENTS_FILE" << EOF @@ -70,9 +97,71 @@ cat > "$ENTITLEMENTS_FILE" << EOF EOF +else + # Dev: needs application-identifier for provisioning profile + cat > "$ENTITLEMENTS_FILE" << EOF + + + + + com.apple.application-identifier + ${TEAM_ID}.${BUNDLE_ID} + keychain-access-groups + + ${TEAM_ID}.* + + + +EOF +fi + +if [ "$MODE" = "release" ]; then + # Release: Developer ID signing with hardened runtime (required for notarization) + codesign --force --sign "$IDENTITY" \ + --options runtime \ + --timestamp \ + --entitlements "$ENTITLEMENTS_FILE" \ + "$APP" + + rm -f "$ENTITLEMENTS_FILE" + + echo "✓ Signed with Developer ID" + + # Create zip for notarization + ZIP="target/release/plc-touch.zip" + rm -f "$ZIP" + ditto -c -k --keepParent "$APP" "$ZIP" -codesign --force --sign "$IDENTITY" --entitlements "$ENTITLEMENTS_FILE" "$APP" -rm -f "$ENTITLEMENTS_FILE" + echo "Submitting for notarization..." + APPLE_ID_ARG="${APPLE_ID:?Set APPLE_ID in .env for notarization}" + PASS_ARG="${NOTARIZE_PASSWORD:?Set NOTARIZE_PASSWORD in .env (app-specific password)}" -echo "✓ Built and signed $APP" -echo " Run with: $APP/Contents/MacOS/plc-touch" + xcrun notarytool submit "$ZIP" \ + --apple-id "$APPLE_ID_ARG" \ + --team-id "$TEAM_ID" \ + --password "$PASS_ARG" \ + --wait + + # Staple the notarization ticket to the app + xcrun stapler staple "$APP" + + # Re-create zip with stapled app + rm -f "$ZIP" + ditto -c -k --keepParent "$APP" "$ZIP" + + echo "" + echo "✓ Built, signed, notarized, and stapled" + echo " Distribute: $ZIP" + echo " Run with: $APP/Contents/MacOS/plc-touch" +else + # Dev: Apple Development signing + codesign --force --sign "$IDENTITY" \ + --entitlements "$ENTITLEMENTS_FILE" \ + "$APP" + + rm -f "$ENTITLEMENTS_FILE" + + echo "" + echo "✓ Built and signed (dev)" + echo " Run with: $APP/Contents/MacOS/plc-touch" +fi diff --git a/src/enclave.rs b/src/enclave.rs index 5a63479..ade7f79 100644 --- a/src/enclave.rs +++ b/src/enclave.rs @@ -145,7 +145,7 @@ pub fn generate_key(label: &str, syncable: bool) -> Result { CFString::wrap_under_get_rule(kSecAttrSynchronizable), CFBoolean::true_value().as_CFType(), )); - // Use explicit access group so the key is findable across devices + // Explicit access group so the key is findable across devices attrs_pairs.push(( CFString::wrap_under_get_rule(kSecAttrAccessGroup), CFString::new(keychain_access_group()).as_CFType(),