diff options
| author | David Lönnhager <david.l@mullvad.net> | 2025-02-24 19:30:54 +0100 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2025-03-05 23:32:36 +0100 |
| commit | a193f5bf70500bd1b10edf2904c986419c18748f (patch) | |
| tree | f55313d61c431ca79ae61f01cdf15fcd94cdd73e | |
| parent | b5964d5b9f64089d99e29925fc5f578e7cf75b24 (diff) | |
| download | mullvadvpn-a193f5bf70500bd1b10edf2904c986419c18748f.tar.xz mullvadvpn-a193f5bf70500bd1b10edf2904c986419c18748f.zip | |
Sign and notarize installer downloader
| -rw-r--r-- | .github/workflows/downloader.yml | 4 | ||||
| -rwxr-xr-x | installer-downloader/build.sh | 266 | ||||
| -rw-r--r-- | installer-downloader/src/cacao_impl/mod.rs | 2 | ||||
| -rw-r--r-- | installer-downloader/src/resource.rs | 2 |
4 files changed, 233 insertions, 41 deletions
diff --git a/.github/workflows/downloader.yml b/.github/workflows/downloader.yml index c8c90716ae..33ce992fb7 100644 --- a/.github/workflows/downloader.yml +++ b/.github/workflows/downloader.yml @@ -55,7 +55,7 @@ jobs: - name: Check file size uses: ./.github/actions/check-file-size with: - artifact: "./installer-downloader/dist/MullvadDownloader.exe" + artifact: "./dist/Install Mullvad VPN.exe" max_size: ${{ env.MAX_BINARY_SIZE }} build-macos: @@ -77,5 +77,5 @@ jobs: - name: Check file size uses: ./.github/actions/check-file-size with: - artifact: "./installer-downloader/dist/MullvadDownloader.dmg" + artifact: "./dist/Install Mullvad VPN.dmg" max_size: ${{ env.MAX_BINARY_SIZE }} diff --git a/installer-downloader/build.sh b/installer-downloader/build.sh index a425c10ff6..56adbd6be3 100755 --- a/installer-downloader/build.sh +++ b/installer-downloader/build.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# This script is used to build, and optionally sign the downloader, always in release mode. +# This script is used to build, and optionally sign, the downloader, always in release mode. # This script performs the equivalent of the following profile: # @@ -25,8 +25,71 @@ source ../scripts/utils/host source ../scripts/utils/log CARGO_TARGET_DIR=${CARGO_TARGET_DIR:-"../target"} -DIST_DIR="./dist" +export CARGO_TARGET_DIR +# Temporary build directory +BUILD_DIR="./build" +# Successfully built (and signed) artifacts +DIST_DIR="../dist" + +BUNDLE_NAME="MullvadDownloader" +BUNDLE_ID="net.mullvad.$BUNDLE_NAME" + +FILENAME="Install Mullvad VPN" + +rm -rf "$BUILD_DIR" +mkdir -p "$BUILD_DIR" + +mkdir -p "$DIST_DIR" + +# Whether to sign and notarized produced binaries +SIGN="false" + +# Temporary keychain to store the .p12 in. +# This is automatically created/replaced when signing on macOS. +SIGN_KEYCHAIN_PATH="$HOME/Library/Keychains/mv-metadata-keychain-db" + +# Parse arguments +while [[ "$#" -gt 0 ]]; do + case $1 in + --sign) + SIGN="true" + ;; + *) + log_error "Unknown parameter: $1" + exit 1 + ;; + esac + shift +done + +# Check that we have the correct environment set for signing +function assert_can_sign { + if [[ "$(uname -s)" == "Darwin" ]]; then + if [[ -z ${CSC_LINK-} ]]; then + log_error "The variable CSC_LINK is not set. It needs to point to a file containing the private key used for signing of binaries." + exit 1 + fi + if [[ -z ${CSC_KEY_PASSWORD-} ]]; then + read -rsp "CSC_KEY_PASSWORD = " CSC_KEY_PASSWORD + echo "" + export CSC_KEY_PASSWORD + fi + if [[ -z ${NOTARIZE_KEYCHAIN-} || -z ${NOTARIZE_KEYCHAIN_PROFILE-} ]]; then + log_error "The variables NOTARIZE_KEYCHAIN and NOTARIZE_KEYCHAIN_PROFILE must be set." + exit 1 + fi + elif [[ "$(uname -s)" == "MINGW"* ]]; then + if [[ -z ${CERT_HASH-} ]]; then + log_error "The variable CERT_HASH is not set. It needs to be set to the thumbprint of the signing certificate." + exit 1 + fi + fi +} + +# Run cargo with all appropriate flags and options +# Arguments: +# - (optional) target function build_executable { local -a target_args=() @@ -43,16 +106,12 @@ function build_executable { set -u } -function dist_windows_app { - cp "$CARGO_TARGET_DIR/release/installer-downloader.exe" "$DIST_DIR/MullvadDownloader.exe" -} - -# Combine executables on macOS +# Combine executables on macOS. This must be run after build_executable for both x86 and arm64. function lipo_executables { local target_exes target_exes=() - rm -rf "$DIST_DIR/installer-downloader" + rm -rf "$BUILD_DIR/installer-downloader" case $HOST in x86_64-apple-darwin) target_exes=( @@ -67,17 +126,79 @@ function lipo_executables { ;; esac - lipo "${target_exes[@]}" -create -output "$DIST_DIR/installer-downloader" + lipo "${target_exes[@]}" -create -output "$BUILD_DIR/installer-downloader" +} + +# Create temporary keychain for importing $CSC_LINK +function setup_macos_keychain { + log_info "Creating a temporary keychain \"$SIGN_KEYCHAIN_PATH\" for $CSC_LINK" + + SIGN_KEYCHAIN_PASS=$(openssl rand -base64 64) + export SIGN_KEYCHAIN_PASS + + delete_macos_keychain + trap "delete_macos_keychain" EXIT + + /usr/bin/security create-keychain -p "$SIGN_KEYCHAIN_PASS" "$SIGN_KEYCHAIN_PATH" + /usr/bin/security unlock-keychain -p "$SIGN_KEYCHAIN_PASS" "$SIGN_KEYCHAIN_PATH" + /usr/bin/security set-keychain-settings "$SIGN_KEYCHAIN_PATH" + + # Include keychain in the search list, or codesign won't find it + /usr/bin/security list-keychains -d user -s "$SIGN_KEYCHAIN_PATH" + + log_info "Importing PKCS #12 to keychain" + + /usr/bin/security import "$CSC_LINK" -k "$SIGN_KEYCHAIN_PATH" -P "$CSC_KEY_PASSWORD" -T /usr/bin/codesign + + # Prevent password prompt when signing + /usr/bin/security set-key-partition-list -S "apple-tool:,apple:" -s -k "$SIGN_KEYCHAIN_PASS" "$SIGN_KEYCHAIN_PATH" + + log_info "Done." + + # Find identity + log_info "Find the identity to use" + + /usr/bin/security find-identity -p codesigning + read -rp "Enter identity: " SIGN_KEYCHAIN_IDENTITY + export SIGN_KEYCHAIN_IDENTITY + + # TODO: auto-detect identity } +function delete_macos_keychain { + /usr/bin/security delete-keychain "$SIGN_KEYCHAIN_PATH" || true + rm -f "$SIGN_KEYCHAIN_PATH" +} + +# Sign an artifact. +# - setup_macos_keychain must be called first +# Arguments: +# - file to sign +function sign_macos { + local file="$1" + if [[ "$SIGN" == "false" ]]; then + # Ad-hoc sign app bundle + /usr/bin/codesign --identifier "$BUNDLE_ID" \ + --sign - \ + --timestamp=none --verbose=0 -o runtime \ + "$file" + else + /usr/bin/codesign --identifier "$BUNDLE_ID" \ + --sign "$SIGN_KEYCHAIN_IDENTITY" \ + --keychain "$SIGN_KEYCHAIN_PATH" \ + --verbose=0 -o runtime \ + "$file" + fi +} + +# Build app bundle and dmg, and optionally sign it. +# If `$SIGN` is false, the app bundle is only ad-hoc signed. function dist_macos_app { - local app_path - bundle_name="MullvadDownloader" - bundle_id="net.mullvad.$bundle_name" - app_path="$DIST_DIR/$bundle_name.app/" + local app_path="$BUILD_DIR/$FILENAME.app/" + local dmg_path="$BUILD_DIR/$FILENAME.dmg" # Build app bundle - echo "Building $app_path..." + log_info "Building $app_path..." rm -rf "$app_path" @@ -87,37 +208,108 @@ function dist_macos_app { mkdir -p "$app_path/Contents/MacOS" cp ./assets/Info.plist "$app_path/Contents/Info.plist" - cp "$DIST_DIR/installer-downloader" "$app_path/Contents/MacOS/installer-downloader" + cp "$BUILD_DIR/installer-downloader" "$app_path/Contents/MacOS/installer-downloader" - # Ad-hoc sign app bundle - codesign --force --deep --identifier "$bundle_id" --sign - \ - --timestamp=none --verbose=0 -o runtime \ - "$DIST_DIR/$bundle_name.app" + # Sign app bundle + if [[ "$SIGN" != "false" ]]; then + setup_macos_keychain + fi + sign_macos "$app_path" # Pack in .dmg - echo "Creating .dmg image..." + log_info "Creating $dmg_path image..." + hdiutil create -volname "$FILENAME" -srcfolder "$app_path" -ov -format UDZO \ + "$dmg_path" - hdiutil create -volname "MullvadDownloader" -srcfolder "$app_path" -ov -format UDZO \ - "$DIST_DIR/MullvadDownloader.dmg" + # Sign .dmg + sign_macos "$dmg_path" + + # Notarize .dmg + if [[ "$SIGN" != "false" ]]; then + notarize_mac "$dmg_path" + fi - # TODO: sign image? + # Move to dist dir + log_info "Moving final artifacts to $DIST_DIR" + rm -rf "$DIST_DIR/$FILENAME.app/" + rm -rf "$DIST_DIR/$FILENAME.dmg" + mv "$app_path" "$DIST_DIR/" + mv "$dmg_path" "$DIST_DIR/" } -mkdir -p "$DIST_DIR" +# Notarize and staple a file. +# Arguments: +# - file to sign +function notarize_mac { + local file="$1" -if [[ "$(uname -s)" == "Darwin" ]]; then - case $HOST in - x86_64-apple-darwin) TARGETS=("" aarch64-apple-darwin);; - aarch64-apple-darwin) TARGETS=("" x86_64-apple-darwin);; - esac + log_info "Notarizing $file" + xcrun notarytool submit "$file" \ + --keychain "$NOTARIZE_KEYCHAIN" \ + --keychain-profile "$NOTARIZE_KEYCHAIN_PROFILE" \ + --wait + + log_info "Stapling $file" + xcrun stapler staple "$file" +} + +# Sign a file. +# Arguments: +# - file to sign +function sign_win { + local binary=$1 + local num_retries=3 + + for i in $(seq 0 ${num_retries}); do + log_info "Signing $binary..." + if signtool sign \ + -tr http://timestamp.digicert.com -td sha256 \ + -fd sha256 -d "Mullvad VPN installer" \ + -du "https://github.com/mullvad/mullvadvpn-app#readme" \ + -sha1 "$CERT_HASH" "$binary" + then + break + fi - for t in "${TARGETS[@]:-"$HOST"}"; do - build_executable "$t" + if [ "$i" -eq "${num_retries}" ]; then + return 1 + fi + + sleep 1 done +} + +# Copy executable and optionally sign it. +function dist_windows_app { + cp "$CARGO_TARGET_DIR/release/installer-downloader.exe" "$BUILD_DIR/$FILENAME.exe" + if [[ "$SIGN" != "false" ]]; then + sign_win "$BUILD_DIR/$FILENAME.exe" + fi + mv "$BUILD_DIR/$FILENAME.exe" "$DIST_DIR/" +} + +function main { + if [[ "$SIGN" != "false" ]]; then + assert_can_sign + fi + + if [[ "$(uname -s)" == "Darwin" ]]; then + case $HOST in + x86_64-apple-darwin) TARGETS=("" aarch64-apple-darwin);; + aarch64-apple-darwin) TARGETS=("" x86_64-apple-darwin);; + esac + + for t in "${TARGETS[@]:-"$HOST"}"; do + build_executable "$t" + done + + lipo_executables + dist_macos_app + + elif [[ "$(uname -s)" == "MINGW"* ]]; then + build_executable + dist_windows_app + fi +} - lipo_executables - dist_macos_app -elif [[ "$(uname -s)" == "MINGW"* ]]; then - build_executable - dist_windows_app -fi +main diff --git a/installer-downloader/src/cacao_impl/mod.rs b/installer-downloader/src/cacao_impl/mod.rs index 3b607c3295..717223893e 100644 --- a/installer-downloader/src/cacao_impl/mod.rs +++ b/installer-downloader/src/cacao_impl/mod.rs @@ -8,7 +8,7 @@ mod delegate; mod ui; pub fn main() { - let app = App::new("net.mullvad.downloader", AppImpl::default()); + let app = App::new("net.mullvad.MullvadDownloader", AppImpl::default()); // Load "global" values and resources let environment = match Environment::load() { diff --git a/installer-downloader/src/resource.rs b/installer-downloader/src/resource.rs index 07bfe4161e..f235333845 100644 --- a/installer-downloader/src/resource.rs +++ b/installer-downloader/src/resource.rs @@ -1,7 +1,7 @@ //! Shared text and other resources /// Window title -pub const WINDOW_TITLE: &str = "Mullvad VPN downloader"; +pub const WINDOW_TITLE: &str = "Mullvad VPN installer"; /// Window width pub const WINDOW_WIDTH: usize = 600; /// Window height |
