diff --git a/build_dmg.md b/build_dmg.md new file mode 100644 index 0000000..5b36781 --- /dev/null +++ b/build_dmg.md @@ -0,0 +1,100 @@ +Here’s a comprehensive **README content** for your deployment scripts, including instructions to move the `.sh` files up one level. You can tweak it for style or branding: + +--- + +# FIDO2 Manager macOS Deployment Scripts + +This folder contains the deployment scripts for building, packaging, and preparing the **FIDO2 Manager** application on macOS. These scripts automate the entire process from compiling the C++ CLI binary to creating a distributable DMG with the GUI. + +> **Note:** These scripts are intended to be run on macOS (Apple Silicon preferred). + + + +## ⚑ Prerequisites + +Make sure you have the following installed: + +* macOS (13.0 or later recommended) +* [Homebrew](https://brew.sh/) +* Xcode command line tools +* Python 3 +* CMake + +The deployment script will check for and install necessary Homebrew dependencies: + +* pkg-config +* openssl@3 +* libcbor +* zlib +* python-tk + +--- + +## πŸ— Deployment Steps + +1. **These script should be in the project root** +2. **Make the main deployment script executable**: + +```bash +chmod +x deploy_macos.sh +``` + +3. **Run the deployment script**: + +```bash +./deploy_macos.sh +``` + +This will: + +* Set up a Python virtual environment +* Build the C++ CLI (`fido2-token2`) +* Bundle required libraries +* Build the macOS GUI app with PyInstaller +* Fix library linking for macOS +* Optionally code-sign the app if `sign_macos_app.sh` is present +* Create a DMG for distribution + +--- + +## πŸ§ͺ Verification + +After running the script: + +* The final `.app` bundle will be in: + +``` +dist/fido2-manage.app +``` + +* The distributable DMG will be in: + +``` +dist/fido2-manage.dmg +``` + +* The script performs a self-contained check to ensure all required libraries are bundled and CLI commands work. + +--- + +## βš™ Customization + +* **Icon:** Place your `icon.icns` in the project root to replace the placeholder icon. +* **Code signing:** If you have an Apple Developer ID, ensure `sign_macos_app.sh` exists and is executable. The deployment script will prompt to sign the app. + +--- + +## πŸ“ Notes + +* Avoid spaces in your project directory path. The build process handles them but may fail in some cases. +* The script assumes ARM64 architecture. Modify `CMAKE_OSX_ARCHITECTURES` in the script if you need x86_64 support. +* If you encounter missing libraries, check the `staging` folder and ensure `bundle_libs.sh` and `fix_macos_linking.sh` exist. + +--- + +## ⚑ Quick Tips + +* To rebuild from scratch, you can safely delete `build/`, `dist/`, and `.venv/` before running the script. +* Use the final DMG to distribute the app; it contains everything needed to run on other macOS machines. + + \ No newline at end of file diff --git a/bundle_libs.sh b/bundle_libs.sh index 025d892..f3990d5 100644 --- a/bundle_libs.sh +++ b/bundle_libs.sh @@ -34,11 +34,12 @@ process_binary() { echo "${indent}Processing: $(basename "$file_to_fix")" - # Get the list of Homebrew library dependencies, removing trailing colons from otool output - local deps=$(otool -L "$file_to_fix" 2>/dev/null | grep '/opt/homebrew/' | awk '{print $1}' | tr -d ':' | grep -v "^$file_to_fix$" || true) + # Get the list of Homebrew/local library dependencies + # Matches /opt/homebrew/ (Apple Silicon) and /usr/local/ (Intel/Legacy) + local deps=$(otool -L "$file_to_fix" 2>/dev/null | grep -E '/opt/homebrew/|/usr/local/' | awk '{print $1}' | tr -d ':' | grep -v "^$file_to_fix$" || true) if [[ -z "$deps" ]]; then - echo "${indent} No Homebrew dependencies found" + echo "${indent} No external dependencies found" return fi diff --git a/deploy_macos.sh b/deploy_macos.sh new file mode 100644 index 0000000..2f67c60 --- /dev/null +++ b/deploy_macos.sh @@ -0,0 +1,390 @@ +#!/bin/bash +# Complete deployment script for macOS FIDO2 Manager +# This script should be run on the macOS VPS after pulling latest changes + +set -eo pipefail + +# Configuration +APP_NAME="fido2-manage" +CLI_EXECUTABLE_NAME="fido2-token2" +FINAL_APP_NAME="${APP_NAME}.app" +DMG_NAME="${APP_NAME}.dmg" +VOL_NAME="FIDO2 Manager" +BUILD_DIR="build" +DIST_DIR="dist" +STAGING_DIR="${BUILD_DIR}/staging" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Helper Functions +info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +fatal() { + echo -e "${RED}[FATAL]${NC} $1" >&2 + exit 1 +} + +check_command() { + if ! command -v "$1" &> /dev/null; then + fatal "'$1' is not installed. Please install it first." + fi +} + +# Verify we're on macOS +if [[ "$OSTYPE" != "darwin"* ]]; then + fatal "This script must be run on macOS" +fi + +info "Starting FIDO2 Manager deployment on macOS..." + +# 1. Check prerequisites +info "Checking build prerequisites..." +check_command "cmake" +check_command "hdiutil" +check_command "otool" +check_command "install_name_tool" +check_command "python3" + +if ! command -v "brew" &> /dev/null; then + fatal "Homebrew is not installed. Please install it first." +fi + +# 2. Install dependencies +info "Installing/checking Homebrew dependencies..." +dependencies=("pkg-config" "openssl@3" "libcbor" "zlib" "python-tk") +for dep in "${dependencies[@]}"; do + if ! brew list "$dep" &>/dev/null; then + info "Installing dependency: $dep" + brew install "$dep" || fatal "Failed to install $dep" + else + info "βœ“ $dep already installed" + fi +done + +# 3. Setup Python environment +info "Setting up Python virtual environment..." +if [[ -d ".venv" ]]; then + rm -rf ".venv" +fi + +python3 -m venv .venv +source .venv/bin/activate +pip install --upgrade pip +pip install pyinstaller + +# 4. Clean old build artifacts +info "Cleaning old build artifacts..." +rm -rf "$BUILD_DIR" "$DIST_DIR" +rm -f "${APP_NAME}.spec" + +# 5. Build the C++ binary +info "Building C++ binary: ${CLI_EXECUTABLE_NAME}..." +mkdir -p "$STAGING_DIR" + +# Check for spaces in current directory and warn user +current_dir=$(pwd) +if [[ "$current_dir" == *" "* ]]; then + warn "Directory contains spaces: $current_dir" + warn "This may cause build issues. Consider renaming the directory." + warn "Attempting build with space-handling fixes..." +fi + +# Set deployment target to ensure compatibility +cmake -S . -B "$BUILD_DIR" -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES="arm64" -DCMAKE_OSX_DEPLOYMENT_TARGET=13.0 +cmake --build "$BUILD_DIR" --config Release + +CLI_BINARY_PATH="${BUILD_DIR}/tools/${CLI_EXECUTABLE_NAME}" +if [[ ! -f "$CLI_BINARY_PATH" ]]; then + fatal "Build failed. C++ executable not found at: $CLI_BINARY_PATH" +fi + +info "βœ“ C++ binary built successfully" + +# 6. Bundle libraries and fix dependencies +info "Bundling libraries and fixing dependencies..." +if [[ ! -f "./bundle_libs.sh" ]]; then + fatal "bundle_libs.sh not found. Please ensure it exists." +fi + +chmod +x ./bundle_libs.sh +./bundle_libs.sh "$STAGING_DIR" "$CLI_BINARY_PATH" + +# 6.0.5 Copy built libfido2 library (not available via Homebrew) +info "Copying built libfido2 library..." +LIBFIDO2_BUILD_PATH="build/src/libfido2.1.15.0.dylib" +LIBFIDO2_SYMLINK_PATH="build/src/libfido2.1.dylib" + +if [[ -f "$LIBFIDO2_BUILD_PATH" ]]; then + info "βœ“ Found built libfido2 library, copying to staging..." + cp "$LIBFIDO2_BUILD_PATH" "$STAGING_DIR/" + + if [[ -L "$LIBFIDO2_SYMLINK_PATH" ]]; then + # Copy as regular file instead of symlink for better compatibility + cp "$LIBFIDO2_BUILD_PATH" "$STAGING_DIR/libfido2.1.dylib" + else + cp "$LIBFIDO2_SYMLINK_PATH" "$STAGING_DIR/" 2>/dev/null || cp "$LIBFIDO2_BUILD_PATH" "$STAGING_DIR/libfido2.1.dylib" + fi + + info "βœ“ libfido2 library copied to staging directory" +else + warn "libfido2 library not found at: $LIBFIDO2_BUILD_PATH" + warn "App may not work on systems without libfido2 installed" +fi + +# 6.1 Fix library version compatibility +info "Fixing library version compatibility..." +cd "$STAGING_DIR" +if [[ -f "libcbor.0.12.dylib" ]] && [[ ! -f "libcbor.0.11.dylib" ]]; then + # Copy the file instead of creating symlink for better compatibility + cp libcbor.0.12.dylib libcbor.0.11.dylib + info "βœ“ Created libcbor version compatibility copy (safer than symlink)" +fi +# Go back to project root (staging is build/staging, so we need to go up 2 levels) +cd ../.. + +# 6.2 Fix library linking +info "Fixing library linking..." +info "Current directory: $(pwd)" +info "Looking for fix_macos_linking.sh..." + +# Script should be in the project root +LINKING_SCRIPT="./fix_macos_linking.sh" +if [[ ! -f "$LINKING_SCRIPT" ]]; then + info "Files in current directory:" + ls -la *.sh || echo "No .sh files found" + fatal "fix_macos_linking.sh not found at: $(pwd)/$LINKING_SCRIPT" +fi + +chmod +x "$LINKING_SCRIPT" +"$LINKING_SCRIPT" + +# 7. Verify CLI functionality +info "Testing CLI functionality..." +CLI_TEST_PATH="${STAGING_DIR}/${CLI_EXECUTABLE_NAME}" +if [[ -x "$CLI_TEST_PATH" ]]; then + info "Testing CLI help..." + if "$CLI_TEST_PATH" 2>&1 | grep -q "usage:"; then + info "βœ“ CLI help works" + else + warn "CLI help test failed, but continuing..." + fi + + info "Testing CLI device list..." + if "$CLI_TEST_PATH" -L &>/dev/null; then + info "βœ“ CLI device list works" + else + warn "CLI device list test failed (expected if no devices connected)" + fi +else + fatal "CLI binary is not executable" +fi + +# 8. Build macOS app with PyInstaller +info "Building macOS app with PyInstaller..." + +# Create app icon if it doesn't exist +if [[ ! -f "icon.icns" ]]; then + info "Creating placeholder app icon..." + # Try to create a proper icon, but continue if it fails + mkdir -p icon.iconset + # Create a simple 1024x1024 PNG (you can replace this with a proper icon) + echo "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==" | base64 -d > icon.iconset/icon_1024x1024.png + + if command -v iconutil &> /dev/null; then + if iconutil -c icns icon.iconset 2>/dev/null; then + info "βœ“ Icon created successfully" + else + warn "Icon creation failed, continuing without custom icon" + rm -f icon.icns # Remove any partial icon file + fi + else + warn "iconutil not found, continuing without custom icon" + fi + rm -rf icon.iconset +fi + +# Build the app +PYINSTALLER_ARGS=( + --name "$APP_NAME" + --windowed + --noconsole + --add-data "${STAGING_DIR}/*:." + --add-data "fido2-manage-mac.sh:." + --add-binary "${STAGING_DIR}/fido2-token2:." + --osx-bundle-identifier="com.token2.fido2-manager" + --target-arch="arm64" +) + +# Add icon if it exists +if [[ -f "icon.icns" ]]; then + PYINSTALLER_ARGS+=(--icon="icon.icns") + info "Using custom icon" +else + info "Building without custom icon" +fi + +pyinstaller "${PYINSTALLER_ARGS[@]}" gui-mac.py + +# 9. Verify and fix app bundle +APP_BUNDLE_PATH="${DIST_DIR}/${FINAL_APP_NAME}" +if [[ ! -d "$APP_BUNDLE_PATH" ]]; then + fatal "App bundle was not created at: $APP_BUNDLE_PATH" +fi + +info "Verifying app bundle contents..." +BUNDLE_MACOS_DIR="${APP_BUNDLE_PATH}/Contents/MacOS" +BUNDLE_CLI_PATH="${BUNDLE_MACOS_DIR}/fido2-token2" + +# Ensure CLI binary and all libraries are in MacOS directory +info "Ensuring CLI binary and libraries are in MacOS directory..." +if [[ ! -f "$BUNDLE_CLI_PATH" ]]; then + info "Copying CLI binary to app bundle MacOS directory..." + cp "${STAGING_DIR}/fido2-token2" "$BUNDLE_MACOS_DIR/" +fi + +# Copy all libraries to MacOS directory (same directory as binary) +info "Copying all libraries to MacOS directory..." +cp "${STAGING_DIR}"/*.dylib "$BUNDLE_MACOS_DIR/" 2>/dev/null || info "No additional libraries to copy" + +# Copy shell script to bundle (for backward compatibility) +BUNDLE_SCRIPT_PATH="${BUNDLE_MACOS_DIR}/fido2-manage-mac.sh" +if [[ ! -f "$BUNDLE_SCRIPT_PATH" ]]; then + info "Copying macOS shell script to app bundle..." + cp "fido2-manage-mac.sh" "$BUNDLE_MACOS_DIR/" + chmod +x "$BUNDLE_SCRIPT_PATH" +fi + +# Set proper permissions for all executables +chmod +x "$BUNDLE_MACOS_DIR"/* +info "βœ“ App bundle created and verified with consistent binary placement" + +# 10. Test the app bundle +info "Testing app bundle..." +if [[ -f "$BUNDLE_CLI_PATH" ]] && [[ -x "$BUNDLE_CLI_PATH" ]] && [[ -f "$BUNDLE_SCRIPT_PATH" ]] && [[ -x "$BUNDLE_SCRIPT_PATH" ]]; then + info "βœ“ CLI binary and shell script found in app bundle" + + # Test shell script from bundle + if "$BUNDLE_SCRIPT_PATH" -help 2>&1 | grep -q "FIDO2 Token Management Tool"; then + info "βœ“ macOS shell script in app bundle works" + else + warn "macOS shell script in app bundle test failed" + fi +else + fatal "CLI binary or shell script not found or not executable in app bundle" +fi + +# 11. Test GUI (basic check) +info "Testing GUI startup..." +# This will test if the GUI can start without errors +timeout 5 python3 gui-mac.py 2>/dev/null || info "GUI test completed (expected timeout)" + +# 11.5. Code sign the app (OPTIONAL - requires Apple Developer Account) +if [[ -f "./sign_macos_app.sh" ]] && [[ -x "./sign_macos_app.sh" ]]; then + warn "" + warn "Code signing script found. Do you want to sign the app?" + warn "This requires an Apple Developer ID certificate." + read -p "Sign the app? (y/N): " -n 1 -r + echo + if [[ $REPLY =~ ^[Yy]$ ]]; then + info "Code signing app bundle..." + ./sign_macos_app.sh + else + warn "Skipping code signing - app may be blocked by Gatekeeper" + fi +else + warn "No code signing script found - app will not be signed" + warn "Unsigned apps may be blocked by macOS Gatekeeper" + warn "See CODE_SIGNING_GUIDE.md for instructions" +fi + +# 12. Create DMG +info "Creating DMG package..." +FINAL_DMG_PATH="${DIST_DIR}/${DMG_NAME}" +if [[ -f "$FINAL_DMG_PATH" ]]; then + rm -f "$FINAL_DMG_PATH" +fi + +# Create temporary directory for DMG contents +DMG_TEMP_DIR=$(mktemp -d) +cp -R "$APP_BUNDLE_PATH" "$DMG_TEMP_DIR/" +ln -s /Applications "$DMG_TEMP_DIR/Applications" + +# Create the DMG +hdiutil create -fs HFS+ -srcfolder "$DMG_TEMP_DIR" -volname "$VOL_NAME" "$FINAL_DMG_PATH" +rm -rf "$DMG_TEMP_DIR" + +# 13. Final verification and self-contained test +info "Final verification..." +echo "" +echo "=== Build Summary ===" +echo "App bundle: $APP_BUNDLE_PATH" +echo "DMG file: $FINAL_DMG_PATH" +echo "App bundle size: $(du -sh "$APP_BUNDLE_PATH" | cut -f1)" +echo "DMG size: $(du -sh "$FINAL_DMG_PATH" | cut -f1)" + +echo "" +echo "=== App Bundle Contents ===" +ls -la "$BUNDLE_MACOS_DIR" + +echo "" +echo "=== Library Dependencies ===" +otool -L "$BUNDLE_CLI_PATH" + +echo "" +echo "=== Self-Contained Verification ===" +# Test that all libraries are bundled +external_deps=$(otool -L "$BUNDLE_CLI_PATH" | grep -E '/opt/homebrew/|/usr/local/' | grep -v '@executable_path' | grep -v '@rpath' || true) +if [[ -n "$external_deps" ]]; then + warn "External dependencies found:" + echo "$external_deps" + warn "App may not work on systems without these dependencies!" +else + info "βœ… All external dependencies are properly bundled" +fi + +# Check that required library files exist +echo "" +echo "=== Required Library Check ===" +FRAMEWORKS_DIR="$APP_BUNDLE_PATH/Contents/Frameworks" +required_libs=$(otool -L "$BUNDLE_CLI_PATH" | grep '@.*\.dylib' | awk '{print $1}' | sed 's/@executable_path\///g' | sed 's/@rpath\///g') +missing_libs="" + +while IFS= read -r lib; do + if [[ -n "$lib" && ! -f "$FRAMEWORKS_DIR/$lib" ]]; then + missing_libs="${missing_libs}${lib}\n" + fi +done <<< "$required_libs" + +if [[ -n "$missing_libs" ]]; then + warn "Missing required libraries in app bundle:" + echo -e "$missing_libs" + warn "App may fail to launch!" +else + info "βœ… All required libraries are present in app bundle" +fi + +# Test CLI execution +echo "" +echo "=== CLI Functionality Test ===" +if "$BUNDLE_CLI_PATH" 2>&1 | head -1 | grep -q "usage:"; then + info "βœ… CLI binary executes correctly" +else + warn "CLI binary test failed - may indicate linking issues" +fi + +# Clean up +deactivate + +info "βœ… Deployment complete!" +info "Final DMG: $FINAL_DMG_PATH" +info "You can now test the app and distribute the DMG file." \ No newline at end of file diff --git a/fix_macos_linking.sh b/fix_macos_linking.sh index 31da942..e0bf8f8 100644 --- a/fix_macos_linking.sh +++ b/fix_macos_linking.sh @@ -44,32 +44,32 @@ otool -L "$BINARY_PATH" echo "" echo "=== Fixing Library References ===" -# Fix libcbor reference -echo "Fixing libcbor reference..." -install_name_tool -change "/opt/homebrew/opt/libcbor/lib/libcbor.0.11.dylib" "@executable_path/libcbor.0.11.dylib" "$BINARY_PATH" -install_name_tool -change "/opt/homebrew/Cellar/libcbor/0.12.0/lib/libcbor.0.11.dylib" "@executable_path/libcbor.0.11.dylib" "$BINARY_PATH" +# Fix all homebrew/local references dynamically +echo "Checking for external references..." +# capture all dependencies that look like they come from homebrew or local install +# Exclude system libraries (/usr/lib, /System/Library) +external_deps=$(otool -L "$BINARY_PATH" | grep -E '/opt/homebrew/|/usr/local/' | awk '{print $1}' || true) -# Fix OpenSSL reference -echo "Fixing OpenSSL reference..." -install_name_tool -change "/opt/homebrew/opt/openssl@3/lib/libcrypto.3.dylib" "@executable_path/libcrypto.3.dylib" "$BINARY_PATH" - -# Fix libfido2 @rpath reference (from local build) -echo "Fixing libfido2 @rpath reference..." -install_name_tool -change "@rpath/libfido2.1.dylib" "@executable_path/libfido2.1.dylib" "$BINARY_PATH" - -# Fix any other homebrew references -echo "Checking for remaining homebrew references..." -homebrew_deps=$(otool -L "$BINARY_PATH" | grep -E '/opt/homebrew/|/usr/local/' | awk '{print $1}' || true) - -if [[ -n "$homebrew_deps" ]]; then - echo "Found additional homebrew dependencies to fix:" +if [[ -n "$external_deps" ]]; then + echo "Found external dependencies to fix:" while IFS= read -r dep; do if [[ -n "$dep" ]]; then lib_name=$(basename "$dep") echo " Fixing: $dep -> @executable_path/$lib_name" - install_name_tool -change "$dep" "@executable_path/$lib_name" "$BINARY_PATH" + # We use || true to continue if for some reason the change fails (though it shouldn't if otool saw it) + install_name_tool -change "$dep" "@executable_path/$lib_name" "$BINARY_PATH" || echo "WARNING: Failed to change $dep" fi - done <<< "$homebrew_deps" + done <<< "$external_deps" +else + echo "No external dependencies found (or already fixed)." +fi + +# Special handling for libfido2 if it's linked with @rpath +# This is sometimes needed if cmake setup uses RPATH +echo "Checking for @rpath/libfido2..." +if otool -L "$BINARY_PATH" | grep -q "@rpath/libfido2"; then + echo " Fixing @rpath/libfido2 reference..." + install_name_tool -change "@rpath/libfido2.1.dylib" "@executable_path/libfido2.1.dylib" "$BINARY_PATH" || true fi # Fix library IDs for the bundled libraries diff --git a/notarize_app.sh b/notarize_app.sh new file mode 100644 index 0000000..6b53ed8 --- /dev/null +++ b/notarize_app.sh @@ -0,0 +1,104 @@ +#!/bin/bash +# Notarize the app for distribution +# Notarization is required for apps distributed outside the Mac App Store + +set -e + +# Configuration - ALL MUST BE CHANGED! +APPLE_ID="your-apple-id@example.com" # Your Apple ID email +TEAM_ID="TEAMID" # Your Team ID (from developer account) +APP_PASSWORD="xxxx-xxxx-xxxx-xxxx" # App-specific password from appleid.apple.com +DMG_FILE="dist/fido2-manage.dmg" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +error() { + echo -e "${RED}[ERROR]${NC} $1" + exit 1 +} + +# Check configuration +if [[ "$APPLE_ID" == "your-apple-id@example.com" ]]; then + error "Please configure APPLE_ID in this script!" +fi + +if [[ "$TEAM_ID" == "TEAMID" ]]; then + error "Please configure TEAM_ID in this script!" +fi + +if [[ "$APP_PASSWORD" == "xxxx-xxxx-xxxx-xxxx" ]]; then + error "Please configure APP_PASSWORD in this script!" +fi + +# Check if DMG exists +if [[ ! -f "$DMG_FILE" ]]; then + error "DMG not found at $DMG_FILE. Run ./create_signed_dmg.sh first!" +fi + +# Store credentials in keychain (optional but recommended) +info "Setting up notarization credentials..." +xcrun notarytool store-credentials "FIDO2_MANAGER_NOTARIZE" \ + --apple-id "$APPLE_ID" \ + --team-id "$TEAM_ID" \ + --password "$APP_PASSWORD" 2>/dev/null || true + +# Submit for notarization +info "Submitting DMG for notarization..." +info "This may take 5-15 minutes..." + +SUBMISSION_ID=$(xcrun notarytool submit "$DMG_FILE" \ + --keychain-profile "FIDO2_MANAGER_NOTARIZE" \ + --wait 2>&1 | grep "id:" | head -1 | awk '{print $2}') + +if [[ -z "$SUBMISSION_ID" ]]; then + # Fallback to direct credentials if keychain profile fails + info "Using direct credentials..." + xcrun notarytool submit "$DMG_FILE" \ + --apple-id "$APPLE_ID" \ + --team-id "$TEAM_ID" \ + --password "$APP_PASSWORD" \ + --wait +else + info "Submission ID: $SUBMISSION_ID" +fi + +# Get notarization info +info "Checking notarization status..." +xcrun notarytool info "$SUBMISSION_ID" \ + --keychain-profile "FIDO2_MANAGER_NOTARIZE" 2>/dev/null || \ +xcrun notarytool info "$SUBMISSION_ID" \ + --apple-id "$APPLE_ID" \ + --team-id "$TEAM_ID" \ + --password "$APP_PASSWORD" + +# Staple the notarization ticket to the DMG +info "Stapling notarization ticket to DMG..." +xcrun stapler staple "$DMG_FILE" || error "Failed to staple notarization ticket" + +# Verify the stapled DMG +info "Verifying notarized DMG..." +xcrun stapler validate "$DMG_FILE" || error "Validation failed" + +# Final verification +info "Running final security check..." +spctl -a -t open --context context:primary-signature -v "$DMG_FILE" || error "Security check failed" + +info "" +info "βœ… Notarization complete!" +info "" +info "The DMG is now ready for distribution." +info "Users can download and install without security warnings." +info "" +info "Distribution checklist:" +info "[ ] Upload to GitHub Releases" +info "[ ] Update download links" +info "[ ] Test download on clean Mac" +info "[ ] Announce release" \ No newline at end of file diff --git a/sign_macos_app.sh b/sign_macos_app.sh new file mode 100644 index 0000000..aae1cfe --- /dev/null +++ b/sign_macos_app.sh @@ -0,0 +1,163 @@ +#!/bin/bash +# Code signing script for FIDO2 Manager +# This script must be run AFTER building but BEFORE creating the DMG + +set -e + +# Configuration - MUST BE CHANGED! +DEVELOPER_ID="Developer ID Application: Your Name (TEAMID)" # <-- CHANGE THIS! +APP_BUNDLE="dist/fido2-manage.app" +ENTITLEMENTS_FILE="entitlements.plist" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +error() { + echo -e "${RED}[ERROR]${NC} $1" + exit 1 +} + +# Check if developer ID is set +if [[ "$DEVELOPER_ID" == "Developer ID Application: Your Name (TEAMID)" ]]; then + error "Please set DEVELOPER_ID in this script first!" +fi + +# Check if app bundle exists +if [[ ! -d "$APP_BUNDLE" ]]; then + error "App bundle not found at $APP_BUNDLE. Run deploy_macos.sh first!" +fi + +# Create entitlements file if it doesn't exist +if [[ ! -f "$ENTITLEMENTS_FILE" ]]; then + info "Creating entitlements file..." + cat > "$ENTITLEMENTS_FILE" << EOF + + + + + + com.apple.security.app-sandbox + + + + com.apple.security.device.usb + + + + com.apple.security.smartcard + + + + com.apple.security.cs.allow-unsigned-executable-memory + + + + com.apple.security.cs.allow-dyld-environment-variables + + + + com.apple.security.cs.disable-library-validation + + + +EOF +fi + +info "Starting code signing process..." +info "Using identity: $DEVELOPER_ID" + +# Step 1: Sign all dynamic libraries +info "Signing dynamic libraries..." +find "$APP_BUNDLE" -name "*.dylib" -type f | while read -r lib; do + info " Signing: $(basename "$lib")" + codesign --force --sign "$DEVELOPER_ID" \ + --timestamp \ + --options runtime \ + "$lib" || error "Failed to sign $(basename "$lib")" +done + +# Step 2: Sign the fido2-token2 binary +info "Signing fido2-token2 executables..." +find "$APP_BUNDLE" -name "fido2-token2" -type f | while read -r binary; do + info " Signing: $binary" + codesign --force --sign "$DEVELOPER_ID" \ + --timestamp \ + --options runtime \ + --entitlements "$ENTITLEMENTS_FILE" \ + "$binary" || error "Failed to sign fido2-token2" +done + +# Step 3: Sign shell scripts (optional but recommended) +info "Signing shell scripts..." +find "$APP_BUNDLE" -name "*.sh" -type f | while read -r script; do + info " Signing: $(basename "$script")" + codesign --force --sign "$DEVELOPER_ID" \ + --timestamp \ + "$script" || warn "Failed to sign $(basename "$script") - continuing anyway" +done + +# Step 4: Sign Python/PyInstaller files +info "Signing Python components..." +find "$APP_BUNDLE" -name "*.so" -type f | while read -r lib; do + info " Signing: $(basename "$lib")" + codesign --force --sign "$DEVELOPER_ID" \ + --timestamp \ + --options runtime \ + "$lib" || error "Failed to sign $(basename "$lib")" +done + +# Step 5: Sign the main executable +info "Signing main executable..." +main_exec="$APP_BUNDLE/Contents/MacOS/fido2-manage" +if [[ -f "$main_exec" ]]; then + codesign --force --sign "$DEVELOPER_ID" \ + --timestamp \ + --options runtime \ + --entitlements "$ENTITLEMENTS_FILE" \ + "$main_exec" || error "Failed to sign main executable" +fi + +# Step 6: Sign the entire app bundle +info "Signing app bundle..." +codesign --force --deep --sign "$DEVELOPER_ID" \ + --timestamp \ + --options runtime \ + --entitlements "$ENTITLEMENTS_FILE" \ + "$APP_BUNDLE" || error "Failed to sign app bundle" + +# Verify the signature +info "Verifying signature..." +if codesign --verify --deep --strict --verbose=2 "$APP_BUNDLE"; then + info "βœ… App bundle signed successfully!" +else + error "❌ Signature verification failed!" +fi + +# Check signature details +info "" +info "Signature details:" +codesign -dvv "$APP_BUNDLE" 2>&1 | grep -E 'Authority|TeamIdentifier|Timestamp' + +# Verify entitlements +info "" +info "Entitlements summary:" +codesign -d --entitlements - "$APP_BUNDLE" 2>&1 | grep -E 'security\.(usb|smartcard|cs\.)' || true + +info "" +info "βœ… Code signing complete!" +info "" +info "Next steps:" +info "1. Test the app locally: open $APP_BUNDLE" +info "2. Create signed DMG: ./create_signed_dmg.sh" +info "3. Notarize the DMG for distribution: ./notarize_app.sh" \ No newline at end of file