a simple rust terminal ui (tui) for setting up alternative plc rotation keys driven by: secure enclave hardware (not synced) or software-based keys (synced to icloud)
···11+# Development signing (./build.sh or ./build.sh dev)
12CODESIGN_IDENTITY="Apple Development: Your Name (XXXXXXXXXX)"
23BUNDLE_ID="com.yourcompany.plc-touch"
34TEAM_ID="XXXXXXXXXX"
55+66+# Release signing (./build.sh release)
77+# Requires a "Developer ID Application" certificate from developer.apple.com
88+DEVELOPER_ID="Developer ID Application: Your Name (XXXXXXXXXX)"
99+APPLE_ID="your@email.com"
1010+NOTARIZE_PASSWORD="xxxx-xxxx-xxxx-xxxx" # App-specific password from appleid.apple.com
+1
.gitignore
···10101111# Apple signing (contains device UDIDs and developer certificates)
1212embedded.provisionprofile
1313+release.provisionprofile
1314entitlements.plist
14151516# Claude Code local settings
+98-9
build.sh
···1414# CODESIGN_IDENTITY="Apple Development: Your Name (XXXXXXXXXX)"
1515# BUNDLE_ID="com.yourcompany.plc-touch"
1616# TEAM_ID="XXXXXXXXXX"
1717-IDENTITY="${CODESIGN_IDENTITY:?Set CODESIGN_IDENTITY in .env or as env var}"
1717+#
1818+# For release builds, also set:
1919+# DEVELOPER_ID="Developer ID Application: Your Name (XXXXXXXXXX)"
2020+# APPLE_ID="your@email.com"
2121+# NOTARIZE_PASSWORD="app-specific-password"
1822BUNDLE_ID="${BUNDLE_ID:-com.example.plc-touch}"
1923TEAM_ID="${TEAM_ID:-XXXXXXXXXX}"
20242525+MODE="${1:-dev}"
2626+2727+case "$MODE" in
2828+ dev)
2929+ IDENTITY="${CODESIGN_IDENTITY:?Set CODESIGN_IDENTITY in .env}"
3030+ ;;
3131+ release)
3232+ IDENTITY="${DEVELOPER_ID:?Set DEVELOPER_ID in .env for release builds}"
3333+ ;;
3434+ *)
3535+ echo "Usage: ./build.sh [dev|release]"
3636+ echo " dev — Development build with provisioning profile (default)"
3737+ echo " release — Developer ID build with notarization for distribution"
3838+ exit 1
3939+ ;;
4040+esac
4141+4242+echo "Building plc-touch ($MODE)..."
4343+2144KEYCHAIN_ACCESS_GROUP="${TEAM_ID}.${BUNDLE_ID}" cargo build --release
22452346# Create .app bundle
···26492750cp target/release/plc-touch "$APP/Contents/MacOS/plc-touch"
28512929-# Copy provisioning profile if it exists
3030-if [ -f embedded.provisionprofile ]; then
5252+# Embed provisioning profile
5353+if [ "$MODE" = "dev" ] && [ -f embedded.provisionprofile ]; then
3154 cp embedded.provisionprofile "$APP/Contents/embedded.provisionprofile"
5555+elif [ "$MODE" = "release" ] && [ -f release.provisionprofile ]; then
5656+ cp release.provisionprofile "$APP/Contents/embedded.provisionprofile"
3257fi
33583459cat > "$APP/Contents/Info.plist" << EOF
···5479</plist>
5580EOF
56815757-# Generate entitlements from env vars
8282+# Generate entitlements
5883ENTITLEMENTS_FILE=$(mktemp)
5959-cat > "$ENTITLEMENTS_FILE" << EOF
8484+if [ "$MODE" = "release" ]; then
8585+ # Developer ID with provisioning profile: includes application-identifier + keychain-access-groups
8686+ cat > "$ENTITLEMENTS_FILE" << EOF
8787+<?xml version="1.0" encoding="UTF-8"?>
8888+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
8989+<plist version="1.0">
9090+<dict>
9191+ <key>com.apple.application-identifier</key>
9292+ <string>${TEAM_ID}.${BUNDLE_ID}</string>
9393+ <key>keychain-access-groups</key>
9494+ <array>
9595+ <string>${TEAM_ID}.*</string>
9696+ </array>
9797+</dict>
9898+</plist>
9999+EOF
100100+else
101101+ # Dev: needs application-identifier for provisioning profile
102102+ cat > "$ENTITLEMENTS_FILE" << EOF
60103<?xml version="1.0" encoding="UTF-8"?>
61104<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
62105<plist version="1.0">
···70113</dict>
71114</plist>
72115EOF
116116+fi
731177474-codesign --force --sign "$IDENTITY" --entitlements "$ENTITLEMENTS_FILE" "$APP"
7575-rm -f "$ENTITLEMENTS_FILE"
118118+if [ "$MODE" = "release" ]; then
119119+ # Release: Developer ID signing with hardened runtime (required for notarization)
120120+ codesign --force --sign "$IDENTITY" \
121121+ --options runtime \
122122+ --timestamp \
123123+ --entitlements "$ENTITLEMENTS_FILE" \
124124+ "$APP"
125125+126126+ rm -f "$ENTITLEMENTS_FILE"
127127+128128+ echo "✓ Signed with Developer ID"
129129+130130+ # Create zip for notarization
131131+ ZIP="target/release/plc-touch.zip"
132132+ rm -f "$ZIP"
133133+ ditto -c -k --keepParent "$APP" "$ZIP"
134134+135135+ echo "Submitting for notarization..."
136136+ APPLE_ID_ARG="${APPLE_ID:?Set APPLE_ID in .env for notarization}"
137137+ PASS_ARG="${NOTARIZE_PASSWORD:?Set NOTARIZE_PASSWORD in .env (app-specific password)}"
138138+139139+ xcrun notarytool submit "$ZIP" \
140140+ --apple-id "$APPLE_ID_ARG" \
141141+ --team-id "$TEAM_ID" \
142142+ --password "$PASS_ARG" \
143143+ --wait
144144+145145+ # Staple the notarization ticket to the app
146146+ xcrun stapler staple "$APP"
147147+148148+ # Re-create zip with stapled app
149149+ rm -f "$ZIP"
150150+ ditto -c -k --keepParent "$APP" "$ZIP"
761517777-echo "✓ Built and signed $APP"
7878-echo " Run with: $APP/Contents/MacOS/plc-touch"
152152+ echo ""
153153+ echo "✓ Built, signed, notarized, and stapled"
154154+ echo " Distribute: $ZIP"
155155+ echo " Run with: $APP/Contents/MacOS/plc-touch"
156156+else
157157+ # Dev: Apple Development signing
158158+ codesign --force --sign "$IDENTITY" \
159159+ --entitlements "$ENTITLEMENTS_FILE" \
160160+ "$APP"
161161+162162+ rm -f "$ENTITLEMENTS_FILE"
163163+164164+ echo ""
165165+ echo "✓ Built and signed (dev)"
166166+ echo " Run with: $APP/Contents/MacOS/plc-touch"
167167+fi
+1-1
src/enclave.rs
···145145 CFString::wrap_under_get_rule(kSecAttrSynchronizable),
146146 CFBoolean::true_value().as_CFType(),
147147 ));
148148- // Use explicit access group so the key is findable across devices
148148+ // Explicit access group so the key is findable across devices
149149 attrs_pairs.push((
150150 CFString::wrap_under_get_rule(kSecAttrAccessGroup),
151151 CFString::new(keychain_access_group()).as_CFType(),