mirror of
https://github.com/veracrypt/VeraCrypt.git
synced 2026-10-21 22:02:37 +00:00
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:
+134
-101
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user