From 5e064ad2afbbe32a717c976b7d91fde791a7f9ea Mon Sep 17 00:00:00 2001 From: Mounir IDRASSI Date: Sat, 17 Oct 2026 10:50:33 +0200 Subject: [PATCH] macOS: harden notarization and use Keychain credentials Use the new signing identity and Keychain profiles. Verify hardened runtime and packaged binaries before submission. Stop on failures and isolate outputs to prevent stale uploads. --- src/Setup/MacOSX/notarize.sh | 235 ++++++++++++++++++++--------------- 1 file changed, 134 insertions(+), 101 deletions(-) diff --git a/src/Setup/MacOSX/notarize.sh b/src/Setup/MacOSX/notarize.sh index c3e2ff05..981392d3 100755 --- a/src/Setup/MacOSX/notarize.sh +++ b/src/Setup/MacOSX/notarize.sh @@ -1,37 +1,67 @@ #!/bin/bash +# Never continue to an upload after a failed build or signing command. +set -euo pipefail + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # Directory where the script is located BUNDLE_PATH="${SCRIPT_DIR}/../../Main/VeraCrypt.app" # Path to the VeraCrypt.app bundle -APPLE_ID="appleid" # Apple ID -APP_SPECIFIC_PASSWORD="password" # app-specific password -TEAM_ID="teamid" # team ID -SIGNING_ID="signingid" # Signing ID +TEAM_ID="HLTN5GLG99" # team ID +SIGNING_ID="Developer ID Application: Mounir Joseph IDRASSI (HLTN5GLG99)" # Signing ID +INSTALLER_SIGNING_ID="Developer ID Installer: Mounir Joseph IDRASSI (HLTN5GLG99)" + +# Set up credentials once: xcrun notarytool store-credentials "VeraCrypt" +# Enter the Apple ID, app-specific password, and team ID at the interactive prompts. +# Credentials stay in Keychain; only the profile name is passed to notarytool. +# VC_NOTARY_KEYCHAIN optionally selects a custom Keychain containing the profile. +NOTARY_PROFILE="${VC_NOTARY_PROFILE:-VeraCrypt}" +NOTARY_AUTH_ARGS=(--keychain-profile "$NOTARY_PROFILE") +if [ -n "${VC_NOTARY_KEYCHAIN:-}" ]; then + NOTARY_AUTH_ARGS+=(--keychain "$VC_NOTARY_KEYCHAIN") +fi + +fail() { + echo "Error: $*" >&2 + exit 1 +} + +# Check every architecture, including the executable actually placed in the pkg. +verify_app() { + local bundle="$1" + local architectures architecture signature + codesign --verify --deep --strict --verbose=2 "$bundle" + architectures=$(lipo -archs "$bundle/Contents/MacOS/VeraCrypt") + [ -n "$architectures" ] || fail "No architectures found in $bundle" + for architecture in $architectures; do + signature=$(codesign --display --verbose=4 --arch "$architecture" "$bundle" 2>&1) + if ! grep -Eq '^CodeDirectory .*flags=.*[(,]runtime[),]' <<< "$signature"; then + fail "Hardened runtime is missing for $architecture in $bundle" + fi + if ! grep -Fxq "TeamIdentifier=$TEAM_ID" <<< "$signature"; then + fail "Unexpected signing team for $architecture in $bundle" + fi + if ! grep -q '^Timestamp=' <<< "$signature"; then + fail "Secure timestamp is missing for $architecture in $bundle" + fi + done +} # Check if VeraCrypt.app bundle exists -if [ ! -d "$BUNDLE_PATH" ]; then - echo "VeraCrypt.app bundle not found: $BUNDLE_PATH" - exit 1 -fi +VC_BINARY="$BUNDLE_PATH/Contents/MacOS/VeraCrypt" +[ -f "$VC_BINARY" ] || fail "VeraCrypt executable not found: $VC_BINARY" # Retrieve the version of VeraCrypt from Info.plist VC_VERSION=$(/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" "${BUNDLE_PATH}/Contents/Info.plist") +[ -n "$VC_VERSION" ] || fail "The app bundle has no version. Rebuild VeraCrypt first." echo "Notarizing VeraCrypt ${VC_VERSION}..." -# Change to the script directory -cd "${SCRIPT_DIR}" - -xattr -rc "$BUNDLE_PATH" -codesign --timestamp --option runtime --deep --force --entitlements "${SCRIPT_DIR}/VeraCrypt.entitlements.plist" --sign "${SIGNING_ID}" "$BUNDLE_PATH" - -# Check dependencies of the VeraCrypt binary -VC_BINARY="$BUNDLE_PATH/Contents/MacOS/VeraCrypt" -DEPENDENCY_OUTPUT=$(otool -L "$VC_BINARY" | grep libfuse-t.dylib) +# Keep otool failures fatal while allowing the MacFUSE case to have no FUSE-T match. +DEPENDENCY_OUTPUT=$(otool -L "$VC_BINARY") # Determine the correct .pkgproj file based on the dependency PKGPROJ_FILE="${SCRIPT_DIR}/veracrypt.pkgproj" DMG_LABEL="VeraCrypt_${VC_VERSION}.dmg" -if [[ "$DEPENDENCY_OUTPUT" != "" ]]; then +if [[ "$DEPENDENCY_OUTPUT" == *libfuse-t.dylib* ]]; then echo "VeraCrypt is linked against FUSE-T." PKGPROJ_FILE="${SCRIPT_DIR}/veracrypt_fuse-t.pkgproj" DMG_LABEL="VeraCrypt_FUSE-T_${VC_VERSION}.dmg" @@ -39,102 +69,105 @@ else echo "VeraCrypt is linked against standard MacFUSE." fi -/usr/local/bin/packagesbuild "$PKGPROJ_FILE" - -PKG_PATH="${SCRIPT_DIR}/VeraCrypt_${VC_VERSION}.pkg" # Constructed path to the VeraCrypt pkg -productsign --sign "Developer ID Installer: IDRIX (Z933746L2S)" --timestamp "${SCRIPT_DIR}/VeraCrypt ${VC_VERSION}.pkg" "$PKG_PATH" - -if [ ! -f "$PKG_PATH" ]; then - echo "pkg file not found: $PKG_PATH" - exit 1 +# The project controls the output filename independently of the app's Info.plist. +PKG_VERSION=$(/usr/libexec/PlistBuddy -c "Print :PACKAGES:0:PACKAGE_SETTINGS:VERSION" "$PKGPROJ_FILE") +PKG_NAME=$(/usr/libexec/PlistBuddy -c "Print :PROJECT:PROJECT_SETTINGS:NAME" "$PKGPROJ_FILE") +if [ "$PKG_VERSION" != "$VC_VERSION" ] || [ "$PKG_NAME" != "VeraCrypt $VC_VERSION" ]; then + fail "Version mismatch: app is $VC_VERSION, package is $PKG_VERSION ($PKG_NAME). Rebuild VeraCrypt and update the package project before notarizing." fi -# Step 1: Upload PKG to Apple for notarization +# Check credentials before modifying the app or building the installer. +echo "Checking notarization credentials in Keychain profile: $NOTARY_PROFILE" +if ! xcrun notarytool history "${NOTARY_AUTH_ARGS[@]}" --output-format json > /dev/null; then + fail "Cannot authenticate with Keychain profile '$NOTARY_PROFILE'. Set it up using 'xcrun notarytool store-credentials', or check Keychain access and network connectivity." +fi + +# Isolate all generated files so that an earlier installer can never be submitted. +WORK_DIR=$(mktemp -d "${SCRIPT_DIR}/.notarize.XXXXXX") +DMG_MOUNTED=false +cleanup() { + local status=$? + trap - EXIT + if [ "$DMG_MOUNTED" = true ]; then + hdiutil detach "$WORK_DIR/mount" -quiet -force || status=1 + fi + if [ "$status" -eq 0 ]; then + rm -rf "$WORK_DIR" + else + echo "Notarization failed. Working files and logs retained in: $WORK_DIR" >&2 + fi + exit "$status" +} +trap cleanup EXIT + +xattr -rc "$BUNDLE_PATH" +codesign --timestamp --options runtime --deep --force --entitlements "${SCRIPT_DIR}/VeraCrypt.entitlements.plist" --sign "$SIGNING_ID" "$BUNDLE_PATH" +verify_app "$BUNDLE_PATH" + +/usr/local/bin/packagesbuild --build-folder "$WORK_DIR" "$PKGPROJ_FILE" +UNSIGNED_PKG_PATH="$WORK_DIR/$PKG_NAME.pkg" +[ -f "$UNSIGNED_PKG_PATH" ] || fail "Package build did not produce $UNSIGNED_PKG_PATH" +PKG_PATH="$WORK_DIR/VeraCrypt_${VC_VERSION}.pkg" +productsign --sign "$INSTALLER_SIGNING_ID" --timestamp "$UNSIGNED_PKG_PATH" "$PKG_PATH" +pkgutil --check-signature "$PKG_PATH" + +# Validate the payload, not just the source bundle or the installer signature. +pkgutil --expand-full "$PKG_PATH" "$WORK_DIR/expanded" +PACKAGED_BUNDLE="$WORK_DIR/expanded/veracrypt.pkg/Payload/Applications/VeraCrypt.app" +cmp "$VC_BINARY" "$PACKAGED_BUNDLE/Contents/MacOS/VeraCrypt" || fail "The packaged executable differs from the signed app." +cmp "$BUNDLE_PATH/Contents/Info.plist" "$PACKAGED_BUNDLE/Contents/Info.plist" || fail "The packaged Info.plist differs from the signed app." +verify_app "$PACKAGED_BUNDLE" + +# Upload only the newly built, verified package. +RESULT_PATH="$WORK_DIR/notarization_result.json" +STATUS_PATH="$WORK_DIR/notarization_status.json" +LOG_PATH="$WORK_DIR/notarization_log.json" echo "Uploading PKG to Apple for notarization..." -xcrun notarytool submit "$PKG_PATH" --apple-id "$APPLE_ID" --password "$APP_SPECIFIC_PASSWORD" --team-id "$TEAM_ID" --wait --output-format json > notarization_result.json - -# Check if the notarization submission was successful -if [ $? -ne 0 ]; then - echo "Failed to submit PKG for notarization." - cat notarization_result.json - exit 1 +if ! xcrun notarytool submit "$PKG_PATH" "${NOTARY_AUTH_ARGS[@]}" --wait --output-format json > "$RESULT_PATH"; then + cat "$RESULT_PATH" + fail "Failed to submit PKG for notarization." fi -# Extract the notarization UUID from the result -REQUEST_UUID=$(grep -o '"id":"[^"]*' notarization_result.json | sed 's/"id":"//') +REQUEST_UUID=$(plutil -extract id raw -expect string -o - "$RESULT_PATH") +STATUS=$(plutil -extract status raw -expect string -o - "$RESULT_PATH") +[ -n "$REQUEST_UUID" ] || fail "Notarization returned an empty request UUID." echo "Notarization Request UUID: $REQUEST_UUID" - -# Step 2: Check the notarization status -echo "Checking notarization status..." -STATUS=$(grep -o '"status":"[^"]*' notarization_result.json | sed 's/"status":"//' | sed 's/"}//') echo "Initial status: $STATUS" -while [ "$STATUS" == "in progress" ]; do +while [[ "$STATUS" == [Ii]n\ [Pp]rogress ]]; do sleep 30 - xcrun notarytool info "$REQUEST_UUID" --apple-id "$APPLE_ID" --password "$APP_SPECIFIC_PASSWORD" --team-id "$TEAM_ID" --output-format json > notarization_status.json - - if [ ! -f notarization_status.json ]; then - echo "Failed to retrieve notarization status." - exit 1 - fi - - STATUS=$(grep -o '"status":"[^"]*' notarization_status.json | sed 's/"status":"//' | sed 's/"}//') + xcrun notarytool info "$REQUEST_UUID" "${NOTARY_AUTH_ARGS[@]}" --output-format json > "$STATUS_PATH" + STATUS=$(plutil -extract status raw -expect string -o - "$STATUS_PATH") echo "Current status: $STATUS" done -# Step 3: Staple the notarization ticket to the pkg or retrieve the log -if [ "$STATUS" == "Accepted" ]; then - echo "Stapling the notarization ticket to the pkg..." - # Staple the notarization ticket to the installer package - xcrun stapler staple "$PKG_PATH" - echo "Notarization and stapling completed successfully." - - # Clean up any existing temporary files and directories - rm -f template.dmg - rm -fr VeraCrypt_dmg - - # Create a directory to mount the template DMG - echo "Creating directory to mount the template DMG..." - mkdir -p VeraCrypt_dmg - - # Decompress the template DMG file - echo "Decompressing the template DMG file..." - bunzip2 -k -f template.dmg.bz2 - - # Attach the template DMG to the system, mount it at VeraCrypt_dmg directory - echo "Attaching the template DMG to the system..." - hdiutil attach template.dmg -noautoopen -quiet -mountpoint VeraCrypt_dmg - - # Copy the notarized installer package into the mounted DMG - echo "Copying the notarized installer package into the mounted DMG..." - cp "VeraCrypt_${VC_VERSION}.pkg" VeraCrypt_dmg/VeraCrypt_Installer.pkg - - # Detach the DMG, ensuring all changes are saved and it's unmounted - echo "Detaching the DMG..." - hdiutil detach VeraCrypt_dmg -quiet -force - - # Convert the DMG back to a compressed format (UDZO) and create the final DMG file - echo "Converting the DMG back to a compressed format..." - rm -f "${DMG_LABEL}" - hdiutil convert template.dmg -quiet -format UDZO -imagekey zlib-level=9 -o "${DMG_LABEL}" - - # Sign the final DMG file - echo "Signing the final DMG file..." - codesign -s "${SIGNING_ID}" --timestamp "${DMG_LABEL}" - - # Clean up temporary files and directories - rm -f template.dmg - rm -fr VeraCrypt_dmg -else +if [ "$STATUS" != "Accepted" ]; then echo "Notarization failed. Retrieving log for more details..." - # Retrieve the notarization log for details on why it failed - xcrun notarytool log "$REQUEST_UUID" --apple-id "$APPLE_ID" --password "$APP_SPECIFIC_PASSWORD" --team-id "$TEAM_ID" --output-format json > notarization_log.json - cat notarization_log.json + if xcrun notarytool log "$REQUEST_UUID" "${NOTARY_AUTH_ARGS[@]}" --output-format json > "$LOG_PATH"; then + cat "$LOG_PATH" + fi + fail "Notarization status: $STATUS" fi -# Clean up temporary files -rm -f notarization_result.json -rm -f notarization_status.json -rm -f notarization_log.json +echo "Stapling the notarization ticket to the pkg..." +xcrun stapler staple "$PKG_PATH" +xcrun stapler validate "$PKG_PATH" -exit 0 \ No newline at end of file +echo "Creating the distribution DMG..." +mkdir "$WORK_DIR/mount" +bunzip2 -c "$SCRIPT_DIR/template.dmg.bz2" > "$WORK_DIR/template.dmg" +hdiutil attach "$WORK_DIR/template.dmg" -noautoopen -quiet -mountpoint "$WORK_DIR/mount" +DMG_MOUNTED=true +cp "$PKG_PATH" "$WORK_DIR/mount/VeraCrypt_Installer.pkg" +hdiutil detach "$WORK_DIR/mount" -quiet -force +DMG_MOUNTED=false +hdiutil convert "$WORK_DIR/template.dmg" -quiet -format UDZO -imagekey zlib-level=9 -o "$WORK_DIR/$DMG_LABEL" +codesign -s "$SIGNING_ID" --timestamp "$WORK_DIR/$DMG_LABEL" +codesign --verify --verbose=2 "$WORK_DIR/$DMG_LABEL" + +# Replace release outputs only after notarization and DMG creation both succeed. +mv -f "$PKG_PATH" "$SCRIPT_DIR/VeraCrypt_${VC_VERSION}.pkg" +mv -f "$WORK_DIR/$DMG_LABEL" "$SCRIPT_DIR/$DMG_LABEL" +echo "Notarization completed: $SCRIPT_DIR/$DMG_LABEL" + +exit 0