summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2020-01-30 14:09:04 +0100
committerAndrej Mihajlov <and@mullvad.net>2020-01-30 14:09:04 +0100
commit828f4d54862af2a8876fe07980a94ae3d173211c (patch)
treeb8a9b826c3a65ab9a75391da6d18a40acf5b957c
parent5792f2a4c650487f6eaa80782091ccc1ec501106 (diff)
parent358fcf4dde4a9273977d579207da90305e1a9327 (diff)
downloadmullvadvpn-828f4d54862af2a8876fe07980a94ae3d173211c.tar.xz
mullvadvpn-828f4d54862af2a8876fe07980a94ae3d173211c.zip
Merge branch 'add-build-script-ios'
-rw-r--r--ios/BuildInstructions.md175
-rw-r--r--ios/ExportOptions.plist23
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj24
-rwxr-xr-xios/build.sh140
4 files changed, 352 insertions, 10 deletions
diff --git a/ios/BuildInstructions.md b/ios/BuildInstructions.md
new file mode 100644
index 0000000000..7bd89d59d8
--- /dev/null
+++ b/ios/BuildInstructions.md
@@ -0,0 +1,175 @@
+# Create private key and Certificate Signing Request (CSR)
+
+## Create new private key and CSR
+
+OpenSSL will ask you the password for the private key, make sure to memorize it, you'll need it
+later.
+
+```
+openssl req -new -newkey rsa:4096 \
+ -outform pem -keyform pem \
+ -keyout private_key.pem \
+ -out cert_signing_request \
+ -subj "/C=SE/O=<ORGANIZATION_NAME>/emailAddress=<YOUR_EMAIL>"
+```
+
+## [Alternative] Create CSR using an existing private key
+
+```
+openssl req -new \
+ -outform pem -keyform pem \
+ -key private_key.pem \
+ -out cert_signing_request \
+ -subj "/C=SE/O=<ORGANIZATION_NAME>/emailAddress=<YOUR_EMAIL>"
+```
+
+# Upload Certificate Signing Request (CSR) to Apple
+
+1. Go to https://developer.apple.com/account/resources/certificates/add
+1. Select "Apple Distribution" option from the given list, press "Continue"
+1. Select the previously created `cert_signing_request` file for upload
+1. Download the provided `distribution.cer` on disk
+
+# Download Apple WWDR certificate
+
+WWDR certificate is used to verify the development and distribution certificates issued by Apple.
+
+```
+curl https://developer.apple.com/certificationauthority/AppleWWDRCA.cer -O
+```
+
+# Convert certificates to PEM format
+
+```
+openssl x509 -inform der -outform pem \
+ -in distribution.cer \
+ -out distribution.pem
+
+openssl x509 -inform der -outform pem \
+ -in AppleWWDRCA.cer \
+ -out AppleWWDRCA.pem
+```
+
+# Export private key and certificates
+
+Produce a PKCS12 container with the private key and all certificates. You will be asked to enter two
+passphrases:
+
+1. Private key passphrase
+1. Export passphrase for PKCS12 file
+
+You can store the produced `apple_code_signing.p12` file as a backup to be able to restore the keys
+in the event of hardware failure. However you should always be able to re-create everything from
+scratch.
+
+```
+openssl pkcs12 -export \
+ -inkey private_key.pem \
+ -in distribution.pem \
+ -certfile AppleWWDRCA.pem \
+ -out apple_code_signing.p12 \
+ -name "<FRIENDLY_KEYCHAIN_NAME>"
+```
+
+# Import private key and certificates into Keychain
+
+```
+security import apple_code_signing.p12 -f pkcs12 \
+ -T /usr/bin/codesign \
+ -P <EXPORT_PASSPHRASE>
+```
+
+Note: `-T /usr/bin/codesign` instructs Keychain to suppress password prompt during code signing,
+although you still have to unlock Keychain for that to have any effect. This instruction is
+equivalent to choosing "Always allow" in the password prompt GUI on the first run of `codesign`
+tool.
+
+Note: providing the export passphrase using the `-P` flag is considered unsafe.
+Leave the `-P <EXPORT_PASSPHRASE>` out to enter the passphrase via GUI.
+
+Technically after that you can clean up all created keys and certificates since all of them are
+securely stored in Keychain now.
+
+```
+rm distribution.{pem,cer} \
+ AppleWWDRCA.{pem,cer} \
+ cert_signing_request \
+ apple_code_signing.p12 \
+ private_key.pem
+```
+
+# Create iOS provisioning profiles
+
+We will now create the provisioning profiles listed below using the Apple developer console.
+
+| App ID | Provisioning Profile Name |
+|-------------------------------------|---------------------------|
+| net.mullvad.MullvadVPN | Mullvad VPN Release |
+| net.mullvad.MullvadVPN.PacketTunnel | Packet Tunnel Release |
+
+Follow these steps to add each of provisioning profiles:
+
+1. Go to https://developer.apple.com/account/resources/profiles/add
+1. Choose "App Store" under "Distribution", then hit "Continue"
+1. Choose the App ID (see the table above) and hit "Continue"
+1. Choose the distribution certificate that you had created after uploading the CSR
+ (i.e `<ORGANIZATION_NAME> (Distribution)`)
+1. Type in the profile name (see the table above) and hit "Generate"
+1. Download the certificate in `ios/iOS Provisioning Profiles` directory. Create the directory if it
+ does not exist.
+
+ Note: you can use a different directory for storing provisioning profiles, however in that case,
+ make sure to provide the path to the custom location via `IOS_PROVISIONING_PROFILES_DIR`
+ environment variable when running `build.sh` (more on that later).
+
+# Setup AppStore credentials
+
+For the automatic uploads to work properly, make sure to provide the Apple ID and password via
+environment variables:
+
+1. `IOS_APPLE_ID` - Your Apple ID (Email)
+1. `IOS_APPLE_ID_PASSWORD` - Your Apple ID password or keychain reference (see explanation below)
+
+
+`IOS_APPLE_ID_PASSWORD` accepts a keychain reference in form of `@keychain:<KEYCHAIN_ITEM_NAME>`.
+
+Use app specific password instead of the actual account password and save it to Keychain. The app
+specific password can be created via AppStore Connect, and added to Keychain using the following
+commands (note that `altool` will be authorized to access the saved password):
+
+```
+ALTOOL_BIN=$(xcrun -find altool)
+
+security add-generic-password \
+ -a <APPLE_ID_EMAIL> \
+ -w <APP_SPECIFIC_PASSWORD> \
+ -s <KEYCHAIN_ITEM_NAME> \
+ -T "$ALTOOL_BIN"
+```
+
+# Automated build and deployment
+
+Build script does not bump the build number, so make sure to do that manually and commit to repo:
+
+```
+agvtool bump
+```
+
+1. Run `./ios/build.sh` to build and export the app for upload to AppStore.
+1. Run `./ios/build.sh --deploy` - same as above but also uploads the app to AppStore and
+ makes it available over TestFlight.
+
+# Keychain quirks
+
+It's possible that `codesign` will keep throwing the password prompts for Keychain, in that case try
+running the following commands __after__ importing the credentials into Keychain:
+
+```
+security unlock-keychain <KEYCHAIN>
+security set-key-partition-list -S apple-tool:,apple: -s <KEYCHAIN>
+```
+
+where `<KEYCHAIN>` is the name of the target Keychain where the signing credentials are stored.
+This guide does not use a separate Keychain store, so use then use `login.keychain-db` then.
+
+Reference: https://docs.travis-ci.com/user/common-build-problems/#mac-macos-sierra-1012-code-signing-errors
diff --git a/ios/ExportOptions.plist b/ios/ExportOptions.plist
new file mode 100644
index 0000000000..6306c6456f
--- /dev/null
+++ b/ios/ExportOptions.plist
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>method</key>
+ <string>app-store</string>
+ <key>teamID</key>
+ <string>G7CDBEG477</string>
+ <key>signingStyle</key>
+ <string>manual</string>
+ <key>uploadSymbols</key>
+ <true/>
+ <key>signingCertificate</key>
+ <string>Apple Distribution: Mullvad VPN AB</string>
+ <key>provisioningProfiles</key>
+ <dict>
+ <key>net.mullvad.MullvadVPN</key>
+ <string>Mullvad VPN Release</string>
+ <key>net.mullvad.MullvadVPN.PacketTunnel</key>
+ <string>Packet Tunnel Release</string>
+ </dict>
+</dict>
+</plist>
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index 49a7d0a18c..12aa54ffba 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -814,7 +814,8 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
- CODE_SIGN_IDENTITY = "iPhone Developer";
+ CODE_SIGN_IDENTITY = "Apple Development";
+ CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -875,7 +876,8 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
- CODE_SIGN_IDENTITY = "iPhone Developer";
+ CODE_SIGN_IDENTITY = "Apple Distribution";
+ CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
@@ -905,8 +907,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = MullvadVPN/MullvadVPN.entitlements;
- CODE_SIGN_IDENTITY = "iPhone Developer";
- CODE_SIGN_STYLE = Automatic;
+ CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_TEAM = G7CDBEG477;
INFOPLIST_FILE = MullvadVPN/Info.plist;
@@ -918,11 +919,12 @@
MARKETING_VERSION = 2020.1;
PRODUCT_BUNDLE_IDENTIFIER = net.mullvad.MullvadVPN;
PRODUCT_NAME = "$(TARGET_NAME)";
- PROVISIONING_PROFILE_SPECIFIER = "";
+ PROVISIONING_PROFILE_SPECIFIER = "Mullvad VPN Development";
SWIFT_OBJC_BRIDGING_HEADER = "MullvadVPN/MullvadVPN-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
+ VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
@@ -933,8 +935,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = MullvadVPN/MullvadVPN.entitlements;
- CODE_SIGN_IDENTITY = "iPhone Developer";
- CODE_SIGN_STYLE = Automatic;
+ CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_TEAM = G7CDBEG477;
INFOPLIST_FILE = MullvadVPN/Info.plist;
@@ -946,10 +947,11 @@
MARKETING_VERSION = 2020.1;
PRODUCT_BUNDLE_IDENTIFIER = net.mullvad.MullvadVPN;
PRODUCT_NAME = "$(TARGET_NAME)";
- PROVISIONING_PROFILE_SPECIFIER = "";
+ PROVISIONING_PROFILE_SPECIFIER = "Mullvad VPN Release";
SWIFT_OBJC_BRIDGING_HEADER = "MullvadVPN/MullvadVPN-Bridging-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
+ VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
@@ -958,7 +960,7 @@
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = PacketTunnel/PacketTunnel.entitlements;
- CODE_SIGN_STYLE = Automatic;
+ CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_TEAM = G7CDBEG477;
ENABLE_BITCODE = NO;
@@ -971,6 +973,7 @@
MARKETING_VERSION = 2020.1;
PRODUCT_BUNDLE_IDENTIFIER = net.mullvad.MullvadVPN.PacketTunnel;
PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE_SPECIFIER = "Packet Tunnel Development";
SKIP_INSTALL = YES;
SWIFT_OBJC_BRIDGING_HEADER = "PacketTunnel/PacketTunnel-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@@ -984,7 +987,7 @@
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = PacketTunnel/PacketTunnel.entitlements;
- CODE_SIGN_STYLE = Automatic;
+ CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_TEAM = G7CDBEG477;
ENABLE_BITCODE = NO;
@@ -997,6 +1000,7 @@
MARKETING_VERSION = 2020.1;
PRODUCT_BUNDLE_IDENTIFIER = net.mullvad.MullvadVPN.PacketTunnel;
PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE_SPECIFIER = "Packet Tunnel Release";
SKIP_INSTALL = YES;
SWIFT_OBJC_BRIDGING_HEADER = "PacketTunnel/PacketTunnel-Bridging-Header.h";
SWIFT_VERSION = 5.0;
diff --git a/ios/build.sh b/ios/build.sh
new file mode 100755
index 0000000000..52f0fa00ca
--- /dev/null
+++ b/ios/build.sh
@@ -0,0 +1,140 @@
+#!/usr/bin/env bash
+
+# This script is used to build and ship the iOS app
+set -eu
+shopt -s nullglob
+
+SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+
+###########################################
+# Verify environment configuration
+###########################################
+
+if [[ -z ${IOS_APPLE_ID-} ]]; then
+ echo "The variable IOS_APPLE_ID is not set."
+ exit
+fi
+
+if [[ -z ${IOS_APPLE_ID_PASSWORD-} ]]; then
+ echo "The variable IOS_APPLE_ID_PASSWORD is not set."
+ read -sp "IOS_APPLE_ID_PASSWORD = " IOS_APPLE_ID_PASSWORD
+ echo ""
+ export IOS_APPLE_ID_PASSWORD
+fi
+
+# Provisioning profiles directory
+if [[ -z ${IOS_PROVISIONING_PROFILES_DIR-} ]]; then
+ IOS_PROVISIONING_PROFILES_DIR="$SCRIPT_DIR/iOS Provisioning Profiles"
+
+ echo "The variable IOS_PROVISIONING_PROFILES_DIR is not set."
+ echo "Default: $IOS_PROVISIONING_PROFILES_DIR"
+
+ export IOS_PROVISIONING_PROFILES_DIR
+fi
+
+###########################################
+# Build configuration
+###########################################
+
+# The Xcode project name without file extension
+# The folder with all sources is expected to hold the same name
+PROJECT_NAME="MullvadVPN"
+
+# Xcode project directory
+XCODE_PROJECT_DIR="$SCRIPT_DIR/$PROJECT_NAME.xcodeproj"
+
+# Build output directory without trailing slash
+BUILD_OUTPUT_DIR="$SCRIPT_DIR/Build"
+
+# Xcode archive output
+XCODE_ARCHIVE_DIR="$BUILD_OUTPUT_DIR/$PROJECT_NAME.xcarchive"
+
+# Export options file used for producing .xcarchive
+EXPORT_OPTIONS_PATH="$SCRIPT_DIR/ExportOptions.plist"
+
+# Path to generated IPA file produced after .xcarchive export
+IPA_PATH="$BUILD_OUTPUT_DIR/$PROJECT_NAME.ipa"
+
+# Xcodebuild intermediate files directory
+DERIVED_DATA_DIR="$BUILD_OUTPUT_DIR/DerivedData"
+
+# System provisioning profiles directory
+SYSTEM_PROVISIONING_PROFILES_DIR="$HOME/Library/MobileDevice/Provisioning Profiles"
+
+
+###########################################
+# Install provisioning profiles
+###########################################
+
+get_mobile_provisioning_uuid() {
+ security cms -D -i "$1" | grep -aA1 UUID | grep -o "[-a-zA-Z0-9]\{36\}"
+}
+
+install_mobile_provisioning() {
+ echo "Install system provisioning profiles into $SYSTEM_PROVISIONING_PROFILES_DIR"
+
+ if [[ ! -d "$SYSTEM_PROVISIONING_PROFILES_DIR" ]]; then
+ echo "Missing system provisioning profiles directory. Creating one."
+ mkdir -p "$SYSTEM_PROVISIONING_PROFILES_DIR"
+ fi
+
+ for mobile_provisioning_path in "$IOS_PROVISIONING_PROFILES_DIR"/*.mobileprovision; do
+ local profile_uuid=$(get_mobile_provisioning_uuid "$mobile_provisioning_path")
+ local target_path="$SYSTEM_PROVISIONING_PROFILES_DIR/$profile_uuid.mobileprovision"
+
+ if [[ -f "$target_path" ]]; then
+ echo "Skip installing $mobile_provisioning_path"
+ else
+ echo "Install $mobile_provisioning_path -> $target_path"
+
+ cp "$mobile_provisioning_path" "$target_path"
+ fi
+ done
+}
+
+install_mobile_provisioning
+
+
+###########################################
+# Build Xcode project
+###########################################
+
+release_build() {
+ xcodebuild \
+ -project "$XCODE_PROJECT_DIR" \
+ -scheme "$PROJECT_NAME" \
+ -sdk iphoneos \
+ -configuration Release \
+ -derivedDataPath "$DERIVED_DATA_DIR" \
+ $@
+}
+
+# Clean build directory
+release_build clean
+
+# Archive project
+release_build archive -archivePath "$XCODE_ARCHIVE_DIR"
+
+# Export IPA for distribution
+xcodebuild \
+ -exportArchive \
+ -archivePath "$XCODE_ARCHIVE_DIR" \
+ -exportOptionsPlist "$EXPORT_OPTIONS_PATH" \
+ -exportPath "$BUILD_OUTPUT_DIR"
+
+
+###########################################
+# Deploy to AppStore
+###########################################
+
+if [[ "${1:-""}" == "--deploy" ]]; then
+ xcrun altool \
+ --upload-app --verbose \
+ --type ios \
+ --file "$IPA_PATH" \
+ --username "$IOS_APPLE_ID" \
+ --password "$IOS_APPLE_ID_PASSWORD"
+else
+ echo "Deployment to AppStore will not be performed."
+ echo "Run with --deploy to upload the binary."
+fi