diff --git a/ci/Jenkinsfile.fdroid b/ci/Jenkinsfile.fdroid index e5634990da4..234a246771a 100644 --- a/ci/Jenkinsfile.fdroid +++ b/ci/Jenkinsfile.fdroid @@ -93,6 +93,45 @@ pipeline { } } + stage('Sign') { + steps { + script { + /* Auto-selects release keystore on release/nightly, PR keystore otherwise. */ + def keystore = creds.androidKeystorePrefix() + withCredentials([ + file( + credentialsId: "${keystore}-file", + variable: 'KEYSTORE_PATH' + ), + string( + credentialsId: "${keystore}-pass", + variable: 'KEYSTORE_PASSWORD' + ), + usernamePassword( + credentialsId: "${keystore}-key-pass", + usernameVariable: 'KEYSTORE_ALIAS', + passwordVariable: 'KEYSTORE_KEY_PASSWORD' + ), + ]) { + /* apksigner is provided by the fdroid agent image (fdroid/Dockerfile). + * The F-Droid build emits a zipaligned, unsigned APK, so signing + * in place is sufficient. Passwords are passed via env: provider + * to keep them off the process command line. */ + sh ''' + set +x + apksigner sign \ + --ks "${KEYSTORE_PATH}" \ + --ks-pass "env:KEYSTORE_PASSWORD" \ + --ks-key-alias "${KEYSTORE_ALIAS}" \ + --key-pass "env:KEYSTORE_KEY_PASSWORD" \ + "${STATUS_FDROID_APK}" + apksigner verify --verbose "${STATUS_FDROID_APK}" + ''' + } + } + } + } + stage('Upload') { steps { script { diff --git a/fdroid/build-qt.sh b/fdroid/build-qt.sh index dc1dc4146ff..1529d4fe5a8 100755 --- a/fdroid/build-qt.sh +++ b/fdroid/build-qt.sh @@ -6,6 +6,23 @@ QT_VERSION="${QT_VERSION:-6.9.2}" QT_MODULES=qtbase,qtdeclarative,qt5compat,qtmultimedia,qtshadertools,qtimageformats,qtwebview,qtscxml,qtsvg,qtconnectivity,qtwebsockets,qtpositioning,qtlottie,qtwebchannel (cd "$QT_SRCDIR" && perl init-repository --module-subset="$QT_MODULES") +# Reproducibility: +# add_link_options: strips .note.gnu.build-id from every .so +# add_compile_options: make sure that paths dont leak into final build. +QT5_CMAKELISTS="$QT_SRCDIR/CMakeLists.txt" +if ! grep -q 'build-id=none' "$QT5_CMAKELISTS"; then + sed -i '/^project(Qt$/,/^)$/{/^)$/a\ +add_link_options("LINKER:--build-id=none")\ +add_compile_options("-ffile-prefix-map=${CMAKE_SOURCE_DIR}=.")\ +add_compile_options("-ffile-prefix-map=${CMAKE_BINARY_DIR}=.")\ +add_compile_options("-ffile-prefix-map=$ENV{HOME}=.") +}' "$QT5_CMAKELISTS" +fi + +# Reproducibility: rewrite absolute build/install paths embedded in compiled +# objects (debug strings, __FILE__, etc.) so they match across rebuilders. +QT_REPRO_CFLAGS="-ffile-prefix-map=${QT_SRCDIR}=. -ffile-prefix-map=${BUILD_DIR}=. -ffile-prefix-map=${HOME}=." + # Build Qt for host (required as cross-compilation toolchain for Android) mkdir -p build_qt_host && cd build_qt_host @@ -18,6 +35,8 @@ mkdir -p build_qt_host && cd build_qt_host -nomake tests \ -- \ -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_C_FLAGS_INIT="$QT_REPRO_CFLAGS" \ + -DCMAKE_CXX_FLAGS_INIT="$QT_REPRO_CFLAGS" \ -DCMAKE_MESSAGE_LOG_LEVEL=WARNING \ -Wno-dev @@ -50,6 +69,8 @@ mkdir -p build_qt_android && cd build_qt_android -DFFMPEG_DIR="$FFMPEG_DIR" \ -DFEATURE_ffmpeg=ON \ -DQT_DEFAULT_MEDIA_BACKEND=ffmpeg \ + -DCMAKE_C_FLAGS_INIT="$QT_REPRO_CFLAGS" \ + -DCMAKE_CXX_FLAGS_INIT="$QT_REPRO_CFLAGS" \ -DCMAKE_MESSAGE_LOG_LEVEL=WARNING \ -Wno-dev diff --git a/fdroid/generate-keystore.sh b/fdroid/generate-keystore.sh deleted file mode 100755 index e4b4c4e872f..00000000000 --- a/fdroid/generate-keystore.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env bash -set -e - -# Generate a single-use keystore for signing the F-Droid APK. -# Intended to be sourced: `source generate-keystore.sh ` -# Exports: FDROID_STORE_FILE, FDROID_STORE_PASSWORD, FDROID_KEY_ALIAS, FDROID_KEY_PASSWORD - -if [[ -z "${1:-}" ]]; then - echo "Usage: source generate-keystore.sh " >&2 - return 1 2>/dev/null || exit 1 -fi - -KEYSTORE_PATH="$1" -FDROID_KEY_ALIAS="status-fdroid" -FDROID_STORE_PASSWORD=$(openssl rand -base64 16) -FDROID_KEY_PASSWORD="$FDROID_STORE_PASSWORD" - -mkdir -p "$(dirname "$KEYSTORE_PATH")" -rm -f "$KEYSTORE_PATH" - -keytool -genkey -v \ - -keyalg RSA \ - -keysize 2048 \ - -validity 10000 \ - -deststoretype pkcs12 \ - -dname "CN=Status, OU=Mobile, O=Status Research, L=Zug, S=Zug, C=CH" \ - -keystore "$KEYSTORE_PATH" \ - -alias "$FDROID_KEY_ALIAS" \ - -storepass "$FDROID_STORE_PASSWORD" \ - -keypass "$FDROID_KEY_PASSWORD" \ - >&2 - -export FDROID_STORE_FILE="$KEYSTORE_PATH" -export FDROID_STORE_PASSWORD -export FDROID_KEY_ALIAS -export FDROID_KEY_PASSWORD diff --git a/fdroid/sign-apk.sh b/fdroid/sign-apk.sh deleted file mode 100755 index 3e45cad71c2..00000000000 --- a/fdroid/sign-apk.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env bash -# -# Sign an unsigned APK with zipalign + apksigner. -# -# Called by mobile/scripts/buildApp.sh when Gradle produces an unsigned APK -# (e.g. fdroid builds where signing configs are stripped by fdroid's -# remove_signing_keys). -# See: https://gitlab.com/fdroid/fdroidserver/-/blob/master/fdroidserver/common.py#L3427 -# -# Required environment variables: -# APK_OUT_UNSIGNED - path to the unsigned APK produced by Gradle -# APK_OUT - desired output path for the signed APK -# ANDROID_HOME - Android SDK root (for build-tools) -# FDROID_STORE_FILE - keystore path -# FDROID_KEY_ALIAS - key alias in the keystore -# FDROID_STORE_PASSWORD - keystore password -# FDROID_KEY_PASSWORD - key password -# -set -eou pipefail - -ZIPALIGN=$(find "$ANDROID_HOME/build-tools" -name zipalign | sort -V | tail -1) -APKSIGNER=$(find "$ANDROID_HOME/build-tools" -name apksigner | sort -V | tail -1) - -if [[ -z "$ZIPALIGN" || -z "$APKSIGNER" ]]; then - echo "Error: zipalign or apksigner not found in ANDROID_HOME=$ANDROID_HOME" >&2 - exit 1 -fi - -APK_DIR="$(dirname "$APK_OUT")" -APK_ALIGNED="${APK_DIR}/$(basename "${APK_OUT_UNSIGNED%.apk}")-aligned.apk" - -"$ZIPALIGN" -f 4 "$APK_OUT_UNSIGNED" "$APK_ALIGNED" - -"$APKSIGNER" sign \ - --ks "$FDROID_STORE_FILE" \ - --ks-key-alias "$FDROID_KEY_ALIAS" \ - --ks-pass "pass:$FDROID_STORE_PASSWORD" \ - --key-pass "pass:$FDROID_KEY_PASSWORD" \ - --out "$APK_OUT" \ - "$APK_ALIGNED" - -"$APKSIGNER" verify "$APK_OUT" -echo "APK signed and verified: $APK_OUT" -rm -f "$APK_ALIGNED" "$APK_OUT_UNSIGNED" diff --git a/mobile/Makefile b/mobile/Makefile index f2db51c9e8f..cab9de18b97 100644 --- a/mobile/Makefile +++ b/mobile/Makefile @@ -33,6 +33,11 @@ $(STATUS_GO_LIB): @echo "Building status-go mobile library" @mkdir -p $(LIB_PATH) ifeq ($(OS),android) + # Reproducibility flags for status-go (GOFLAGS,CGO_CFLAGS,CGO_CXXFLAGS,GOMODCACHE): + GOFLAGS="-trimpath -buildvcs=false" \ + GOMODCACHE="$(BUILD_PATH)/.gomodcache" \ + CGO_CFLAGS="-ffile-prefix-map=$(HOME)=." \ + CGO_CXXFLAGS="-ffile-prefix-map=$(HOME)=." \ CC="$(CC)" $(MAKE) -C ../vendor/status-go statusgo-android-library \ ARCH=$(ARCH) \ ANDROID_NDK_ROOT="$(ANDROID_NDK_ROOT)" \ @@ -43,6 +48,10 @@ ifeq ($(OS),android) GO_GENERATE_CMD="go generate" \ SHELL=/bin/sh else ifeq ($(OS),ios) + GOFLAGS="-trimpath -buildvcs=false" \ + GOMODCACHE="$(BUILD_PATH)/.gomodcache" \ + CGO_CFLAGS="-ffile-prefix-map=$(HOME)=." \ + CGO_CXXFLAGS="-ffile-prefix-map=$(HOME)=." \ $(MAKE) -C ../vendor/status-go statusgo-ios-library \ ARCH=$(ARCH) \ IPHONE_SDK="$(IPHONE_SDK)" \ diff --git a/mobile/scripts/buildApp.sh b/mobile/scripts/buildApp.sh index d80d3dec477..775629bc467 100755 --- a/mobile/scripts/buildApp.sh +++ b/mobile/scripts/buildApp.sh @@ -48,13 +48,21 @@ if [[ "${OS}" == "android" ]]; then export BUILD_VARIANT export BUNDLE_IDENTIFIER - if [[ "$GRADLE_TARGETS" == *"Fdroid"* ]]; then - # shellcheck source=../../fdroid/generate-keystore.sh - source "$REPO_ROOT/fdroid/generate-keystore.sh" "$REPO_ROOT/status-fdroid.keystore" - fi - + # Reproducibility flags for fdroid rebuilds: + # --build-id=none drops the .note.gnu.build-id section + # -ffile-prefix-map remaps absolute source paths so they don't depend on $HOME or $BUILD_DIR. + QMAKE_REPRO_ARGS=( + "QMAKE_LFLAGS+=-Wl,--build-id=none" + "QMAKE_CFLAGS+=-ffile-prefix-map=${BUILD_DIR}=." + "QMAKE_CFLAGS+=-ffile-prefix-map=${REPO_ROOT}=." + "QMAKE_CFLAGS+=-ffile-prefix-map=${HOME}=." + "QMAKE_CXXFLAGS+=-ffile-prefix-map=${BUILD_DIR}=." + "QMAKE_CXXFLAGS+=-ffile-prefix-map=${REPO_ROOT}=." + "QMAKE_CXXFLAGS+=-ffile-prefix-map=${HOME}=." + ) "$QMAKE_BIN" "$CWD/../wrapperApp/Status.pro" "${QMAKE_CONFIG[@]}" -spec android-clang \ - ANDROID_ABIS="${ANDROID_ABI:-arm64-v8a}" VERSION="$VERSION" "${QMAKE_DEFINES[@]}" -after + ANDROID_ABIS="${ANDROID_ABI:-arm64-v8a}" VERSION="$VERSION" "${QMAKE_DEFINES[@]}" \ + -after "${QMAKE_REPRO_ARGS[@]}" make -j"$(nproc)" apk_install_target @@ -117,18 +125,14 @@ if [[ "${OS}" == "android" ]]; then echo "APK outputs:" find build/outputs/apk -name '*.apk' 2>/dev/null || echo "No APKs found" - # If Gradle produced an unsigned APK (e.g. fdroid build where signing configs - # are stripped by fdroid's remove_signing_keys), sign it via the dedicated script. - if [[ ! -f "$APK_OUT" && -f "$APK_OUT_UNSIGNED" && -n "${FDROID_STORE_FILE:-}" ]]; then - echo "Signing unsigned APK..." - "$REPO_ROOT/fdroid/sign-apk.sh" - fi - # Copy whichever artifacts were built BUILT="" if [[ -f "$APK_OUT" ]]; then cp "$APK_OUT" "$BIN_DIR/${OUTPUT_NAME}.apk" BUILT="$BIN_DIR/${OUTPUT_NAME}.apk" + elif [[ -f "$APK_OUT_UNSIGNED" ]]; then + cp "$APK_OUT_UNSIGNED" "$BIN_DIR/${OUTPUT_NAME}.apk" + BUILT="$BIN_DIR/${OUTPUT_NAME}.apk" fi if [[ -f "$AAB_OUT" ]]; then cp "$AAB_OUT" "$BIN_DIR/${OUTPUT_NAME}.aab"