build: pin the native libsignal library by SHA-256
The libsignal Dart package downloads a prebuilt libsignal_frb from the GitHub releases of djx-y-z/libsignal_dart and only checks it against a checksum file from the same release. Keep our own pins in app/tool/native_libs.sha256 and verify the finished AAB/APK with app/tool/verify_native_libs.sh; any mismatch fails the release build. The pins cover the upstream archives and the library as packaged after the Android Gradle plugin strips it. `--from-upstream` re-derives the packaged hashes from the upstream archives with llvm-strip.
This commit is contained in:
@@ -0,0 +1,25 @@
|
|||||||
|
# Pinned native crypto library (libsignal_frb) shipped in the Android app.
|
||||||
|
#
|
||||||
|
# The Dart package `libsignal` downloads a prebuilt `libsignal_frb` from the
|
||||||
|
# GitHub releases of djx-y-z/libsignal_dart during the build. Its own check
|
||||||
|
# only compares against a checksum file from the same release, so a replaced
|
||||||
|
# release would go unnoticed. These pins live in our repository instead and
|
||||||
|
# tool/verify_native_libs.sh fails the release build on any mismatch.
|
||||||
|
#
|
||||||
|
# package = version of the `libsignal` Dart package in pubspec.lock
|
||||||
|
# archive = SHA-256 of the upstream release archive
|
||||||
|
# https://github.com/djx-y-z/libsignal_dart/releases/tag/libsignal_frb-4.0.9
|
||||||
|
# shipped = SHA-256 of liblibsignal_frb.so inside our AAB/APK. The Android
|
||||||
|
# Gradle plugin strips the library while packaging; the value equals
|
||||||
|
# `llvm-strip --strip-unneeded` (NDK 28) applied to the file from the
|
||||||
|
# archive. `verify_native_libs.sh --from-upstream` re-derives it.
|
||||||
|
#
|
||||||
|
# Updating libsignal = update every line here in the same commit.
|
||||||
|
|
||||||
|
package libsignal 5.0.9
|
||||||
|
archive android-arm64-v8a b03c1f47b07ded392a9a44b217b3d24470f0dce69901c67061c1f1295f96065f
|
||||||
|
archive android-armeabi-v7a 0dc16fbbfe2bf5b3e7758fd2b297c831fee296c55ac19b4f94a9e0086d26b9b0
|
||||||
|
archive android-x86_64 cdb1623a855fa508dd84c025b267eaac846f882637450f00a74199bdf2d85264
|
||||||
|
shipped arm64-v8a 39612f422ed1dc2c7576165c5c169d7911f64de093eeea4ee8d3047b884c1c0a
|
||||||
|
shipped armeabi-v7a b442795a864fe0ae15a1e04a151863244fedda3b8854b2d22ec9b23e59227e78
|
||||||
|
shipped x86_64 59e5925c2d6f5e9496cbe86d1a7d19965dc4060efe94cd452d58ba651fe7a122
|
||||||
Executable
+87
@@ -0,0 +1,87 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Checks the libsignal native library against the pins in native_libs.sha256.
|
||||||
|
#
|
||||||
|
# verify_native_libs.sh <app.aab|app.apk> every ABI in the artifact must carry
|
||||||
|
# exactly the pinned library
|
||||||
|
# verify_native_libs.sh --from-upstream download the upstream archives, check
|
||||||
|
# their pins and re-derive the shipped
|
||||||
|
# hashes (needs llvm-strip from the NDK)
|
||||||
|
#
|
||||||
|
# Exit code 0 = match, anything else = do not ship.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
TOOL_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||||
|
PINS="$TOOL_DIR/native_libs.sha256"
|
||||||
|
LOCK="$TOOL_DIR/../pubspec.lock"
|
||||||
|
LIB=liblibsignal_frb.so
|
||||||
|
|
||||||
|
die() { echo "verify_native_libs: $*" >&2; exit 1; }
|
||||||
|
pin() { awk -v k="$1" -v n="$2" '$1 == k && $2 == n { print $3 }' "$PINS"; }
|
||||||
|
|
||||||
|
pinned_package=$(pin package libsignal)
|
||||||
|
[ -n "$pinned_package" ] || die "no package line in $PINS"
|
||||||
|
|
||||||
|
# A libsignal upgrade without new pins must stop here with a clear reason,
|
||||||
|
# not later as an unexplained hash mismatch.
|
||||||
|
if [ -f "$LOCK" ]; then
|
||||||
|
locked=$(awk '/^ libsignal:$/ { f = 1 } f && /^ version:/ { gsub(/"/, "", $2); print $2; exit }' "$LOCK")
|
||||||
|
[ "$locked" = "$pinned_package" ] ||
|
||||||
|
die "pubspec.lock has libsignal $locked, pins are for $pinned_package — update $PINS"
|
||||||
|
fi
|
||||||
|
|
||||||
|
sha() { sha256sum "$1" | cut -d' ' -f1; }
|
||||||
|
|
||||||
|
verify_artifact() {
|
||||||
|
local artifact=$1 prefix entries abi want got checked=0
|
||||||
|
[ -f "$artifact" ] || die "file not found: $artifact"
|
||||||
|
case "$artifact" in
|
||||||
|
*.aab) prefix=base/lib ;;
|
||||||
|
*.apk) prefix=lib ;;
|
||||||
|
*) die "expected .aab or .apk: $artifact" ;;
|
||||||
|
esac
|
||||||
|
entries=$(unzip -Z1 "$artifact" | grep -E "^$prefix/[^/]+/$LIB\$" || true)
|
||||||
|
[ -n "$entries" ] || die "$LIB not found in $artifact"
|
||||||
|
for entry in $entries; do
|
||||||
|
abi=$(basename "$(dirname "$entry")")
|
||||||
|
want=$(pin shipped "$abi")
|
||||||
|
[ -n "$want" ] || die "no pin for ABI $abi"
|
||||||
|
got=$(unzip -p "$artifact" "$entry" | sha256sum | cut -d' ' -f1)
|
||||||
|
[ "$got" = "$want" ] || die "$abi: $got does not match pinned $want"
|
||||||
|
echo "ok $abi $got"
|
||||||
|
checked=$((checked + 1))
|
||||||
|
done
|
||||||
|
# Every pinned ABI has to be present: a missing one would crash on those devices.
|
||||||
|
for abi in $(awk '$1 == "shipped" { print $2 }' "$PINS"); do
|
||||||
|
grep -q "^$prefix/$abi/$LIB\$" <<<"$entries" || die "$abi missing in $artifact"
|
||||||
|
done
|
||||||
|
echo "verify_native_libs: $checked libraries match the pins"
|
||||||
|
}
|
||||||
|
|
||||||
|
from_upstream() {
|
||||||
|
local strip=${LLVM_STRIP:-} work version url abi want got
|
||||||
|
if [ -z "$strip" ]; then
|
||||||
|
strip=$(ls "${ANDROID_HOME:-/opt/android-sdk}"/ndk/*/toolchains/llvm/prebuilt/*/bin/llvm-strip 2>/dev/null | sort -V | tail -1)
|
||||||
|
fi
|
||||||
|
[ -x "$strip" ] || die "llvm-strip not found, set LLVM_STRIP"
|
||||||
|
# Archive names carry the version of the native crate, not of the Dart package.
|
||||||
|
version=$(sed -n 's/.*releases\/tag\/libsignal_frb-\([0-9.]*\).*/\1/p' "$PINS" | head -1)
|
||||||
|
[ -n "$version" ] || die "no release tag in $PINS"
|
||||||
|
url="https://github.com/djx-y-z/libsignal_dart/releases/download/libsignal_frb-$version"
|
||||||
|
work=$(mktemp -d); trap 'rm -rf "$work"' RETURN
|
||||||
|
for abi in $(awk '$1 == "shipped" { print $2 }' "$PINS"); do
|
||||||
|
curl -fsSL -o "$work/$abi.tar.gz" "$url/libsignal_frb-$version-android-$abi.tar.gz"
|
||||||
|
want=$(pin archive "android-$abi"); got=$(sha "$work/$abi.tar.gz")
|
||||||
|
[ "$got" = "$want" ] || die "archive android-$abi: $got does not match pinned $want"
|
||||||
|
mkdir "$work/$abi" && tar -xzf "$work/$abi.tar.gz" -C "$work/$abi" "$LIB"
|
||||||
|
"$strip" --strip-unneeded "$work/$abi/$LIB"
|
||||||
|
want=$(pin shipped "$abi"); got=$(sha "$work/$abi/$LIB")
|
||||||
|
[ "$got" = "$want" ] || die "stripped $abi: $got does not match pinned $want"
|
||||||
|
echo "ok $abi archive and stripped library match"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1:-}" in
|
||||||
|
--from-upstream) from_upstream ;;
|
||||||
|
""|-h|--help) sed -n '2,10p' "$0"; exit 64 ;;
|
||||||
|
*) verify_artifact "$1" ;;
|
||||||
|
esac
|
||||||
Reference in New Issue
Block a user