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.
This commit is contained in:
Mounir IDRASSI
2026-10-17 10:50:33 +02:00
parent d331726332
commit 5e064ad2af
+134 -101
View File
@@ -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
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