fix(android): build static OpenSSL for Android and initialize keyring-core (#245)

* fix(android): build static OpenSSL for Android and initialize keyring-core

Android doesn't ship a system libcrypto.so, which SQLCipher (via
libsqlite3-sys) requires. This adds a build script that cross-compiles
OpenSSL as a static library for each Android target (aarch64, armv7,
x86_64) and points the Rust build at those per-target installations.

Also adds the Keyring Kotlin class and initializes the NDK context
in MainActivity so keyring-core can access Android's secure storage.

* fix(android): verify OpenSSL tarball SHA-256 integrity after download

The SHA-256 hash was defined but never checked, and the value itself
was truncated (40 chars, not 64). Replace with the correct hash from
the official release and add verification before extracting.

* fix(android): add pipefail and remove tail pipes in OpenSSL build script

set -e alone does not catch failures in pipeline commands. Adding pipefail
ensures configure/make errors propagate instead of being masked by | tail -3.

* fix(android): detect Apple Silicon NDK path and portable SHA-256 check

The Darwin case now inspects uname -m to set HOST_TAG to darwin-arm64 on
Apple Silicon instead of always using darwin-x86_64.

SHA-256 verification falls back from shasum to sha256sum so the script
works on Linux systems that only ship sha256sum. Both tools use the same
output format (hash followed by filename) so awk '{print $1}' works for
either.
This commit is contained in:
JeffG
2026-02-12 18:14:58 +01:00
committed by GitHub
parent d09ac69501
commit 415eb0c0cc
5 changed files with 197 additions and 32 deletions
+22 -29
View File
@@ -89,20 +89,10 @@ export CC_armv7_linux_androideabi="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/$H
export CXX_armv7_linux_androideabi="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/$HOST_TAG/bin/armv7a-linux-androideabi33-clang++"
export AR_armv7_linux_androideabi="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/$HOST_TAG/bin/llvm-ar"
# OpenSSL environment variables for Android cross-compilation
# Point to macOS OpenSSL for the build process (sqlcipher needs headers)
if [ -d "/opt/homebrew/opt/openssl@3" ]; then
export OPENSSL_DIR="/opt/homebrew/opt/openssl@3"
export OPENSSL_INCLUDE_DIR="/opt/homebrew/opt/openssl@3/include"
export OPENSSL_LIB_DIR="/opt/homebrew/opt/openssl@3/lib"
elif [ -d "/usr/local/opt/openssl@3" ]; then
export OPENSSL_DIR="/usr/local/opt/openssl@3"
export OPENSSL_INCLUDE_DIR="/usr/local/opt/openssl@3/include"
export OPENSSL_LIB_DIR="/usr/local/opt/openssl@3/lib"
fi
# Use vendored OpenSSL for cross-compilation to avoid linking issues
export OPENSSL_STATIC=1
# Build static OpenSSL for Android targets (SQLCipher requires libcrypto)
print_step "Building OpenSSL for Android"
./scripts/build_openssl_android.sh
OPENSSL_ANDROID_DIR="$PWD/rust/target/openssl/install"
# Check if required tools are installed
print_step "Checking development environment"
@@ -132,23 +122,26 @@ mkdir -p android/app/src/main/jniLibs/x86_64
print_step "Building for Android architectures"
cd rust
# aarch64 (arm64-v8a) - 64-bit ARM
print_step "Building for aarch64 (arm64-v8a)"
cargo build --target aarch64-linux-android --release --quiet
cp target/aarch64-linux-android/release/librust_lib_whitenoise.so ../android/app/src/main/jniLibs/arm64-v8a/
print_success "Built for aarch64 (arm64-v8a)"
# Build helper: sets per-target OpenSSL env vars and runs cargo build
build_for_target() {
local target="$1"
local label="$2"
local jni_dir="$3"
# armv7 (armeabi-v7a) - 32-bit ARM
print_step "Building for armv7 (armeabi-v7a)"
cargo build --target armv7-linux-androideabi --release --quiet
cp target/armv7-linux-androideabi/release/librust_lib_whitenoise.so ../android/app/src/main/jniLibs/armeabi-v7a/
print_success "Built for armv7 (armeabi-v7a)"
print_step "Building for $label"
OPENSSL_DIR="$OPENSSL_ANDROID_DIR/$target" \
OPENSSL_INCLUDE_DIR="$OPENSSL_ANDROID_DIR/$target/include" \
OPENSSL_LIB_DIR="$OPENSSL_ANDROID_DIR/$target/lib" \
OPENSSL_STATIC=1 \
OPENSSL_NO_VENDOR=1 \
cargo build --target "$target" --release --quiet
cp "target/$target/release/librust_lib_whitenoise.so" "../android/app/src/main/jniLibs/$jni_dir/"
print_success "Built for $label"
}
# x86_64 - 64-bit Intel
print_step "Building for x86_64"
cargo build --target x86_64-linux-android --release --quiet
cp target/x86_64-linux-android/release/librust_lib_whitenoise.so ../android/app/src/main/jniLibs/x86_64/
print_success "Built for x86_64"
build_for_target "aarch64-linux-android" "aarch64 (arm64-v8a)" "arm64-v8a"
build_for_target "armv7-linux-androideabi" "armv7 (armeabi-v7a)" "armeabi-v7a"
build_for_target "x86_64-linux-android" "x86_64" "x86_64"
cd ..
+160
View File
@@ -0,0 +1,160 @@
#!/bin/bash
# Build OpenSSL static libraries for Android targets
# This is needed because libsqlite3-sys (SQLCipher) requires libcrypto,
# and Android doesn't ship a system libcrypto.so.
set -eo pipefail
print_step() {
echo -e "\n\033[1;34m=== $1 ===\033[0m"
}
print_success() {
echo -e "\033[1;32m$1\033[0m"
}
print_error() {
echo -e "\033[1;31m$1\033[0m"
}
# Configuration
OPENSSL_VERSION="3.4.1"
OPENSSL_SHA256="002a2d6b30b58bf4bea46c43bdd96365aaf8daa6c428782aa4feee06da197df3"
ANDROID_API=33
# Resolve paths
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
OPENSSL_BUILD_DIR="$PROJECT_ROOT/rust/target/openssl"
OPENSSL_SRC_DIR="$OPENSSL_BUILD_DIR/openssl-$OPENSSL_VERSION"
OPENSSL_INSTALL_DIR="$OPENSSL_BUILD_DIR/install"
# Auto-detect Android NDK
if [ -z "$ANDROID_NDK_HOME" ]; then
if [ -n "$NDK_HOME" ]; then
ANDROID_NDK_HOME="$NDK_HOME"
elif [ -n "$ANDROID_HOME" ]; then
NDK_DIR="$ANDROID_HOME/ndk"
if [ -d "$NDK_DIR" ]; then
NDK_VERSION=$(ls "$NDK_DIR" | sort -V | tail -n 1)
if [ -n "$NDK_VERSION" ]; then
ANDROID_NDK_HOME="$NDK_DIR/$NDK_VERSION"
fi
fi
fi
fi
if [ -z "$ANDROID_NDK_HOME" ] || [ ! -d "$ANDROID_NDK_HOME" ]; then
print_error "Android NDK not found. Set ANDROID_NDK_HOME or NDK_HOME."
exit 1
fi
# Detect host OS
case "$(uname -s)" in
Darwin*)
case "$(uname -m)" in
arm64|aarch64) HOST_TAG="darwin-arm64" ;;
*) HOST_TAG="darwin-x86_64" ;;
esac
;;
Linux*) HOST_TAG="linux-x86_64" ;;
*) print_error "Unsupported host OS: $(uname -s)"; exit 1 ;;
esac
TOOLCHAIN="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/$HOST_TAG"
# Target configurations: (rust_target openssl_target arch)
TARGETS=(
"aarch64-linux-android:android-arm64:aarch64"
"armv7-linux-androideabi:android-arm:armv7a"
"x86_64-linux-android:android-x86_64:x86_64"
)
build_openssl_for_target() {
local rust_target="$1"
local openssl_target="$2"
local arch="$3"
local install_prefix="$OPENSSL_INSTALL_DIR/$rust_target"
# Skip if already built
if [ -f "$install_prefix/lib/libcrypto.a" ]; then
print_success "OpenSSL already built for $rust_target (skipping)"
return 0
fi
print_step "Building OpenSSL for $rust_target ($openssl_target)"
# Clean and re-extract source for each target (OpenSSL doesn't support out-of-tree builds well)
local build_src="$OPENSSL_BUILD_DIR/build-$rust_target"
rm -rf "$build_src"
cp -r "$OPENSSL_SRC_DIR" "$build_src"
cd "$build_src"
export ANDROID_NDK_ROOT="$ANDROID_NDK_HOME"
export PATH="$TOOLCHAIN/bin:$PATH"
./Configure "$openssl_target" \
-D__ANDROID_API__=$ANDROID_API \
--prefix="$install_prefix" \
--openssldir="$install_prefix/ssl" \
no-shared \
no-tests \
no-ui-console \
no-stdio \
-fPIC
make -j"$(nproc 2>/dev/null || sysctl -n hw.ncpu)" build_libs
make install_dev
cd "$PROJECT_ROOT"
rm -rf "$build_src"
if [ -f "$install_prefix/lib/libcrypto.a" ]; then
print_success "Built OpenSSL for $rust_target"
else
print_error "Failed to build OpenSSL for $rust_target"
exit 1
fi
}
# Download OpenSSL source if needed
if [ ! -d "$OPENSSL_SRC_DIR" ]; then
print_step "Downloading OpenSSL $OPENSSL_VERSION"
mkdir -p "$OPENSSL_BUILD_DIR"
cd "$OPENSSL_BUILD_DIR"
TARBALL="openssl-$OPENSSL_VERSION.tar.gz"
if [ ! -f "$TARBALL" ]; then
curl -LO "https://github.com/openssl/openssl/releases/download/openssl-$OPENSSL_VERSION/$TARBALL"
fi
print_step "Verifying tarball integrity"
if command -v shasum >/dev/null 2>&1; then
ACTUAL_SHA256=$(shasum -a 256 "$TARBALL" | awk '{print $1}')
elif command -v sha256sum >/dev/null 2>&1; then
ACTUAL_SHA256=$(sha256sum "$TARBALL" | awk '{print $1}')
else
print_error "No SHA-256 tool found (need shasum or sha256sum)"
exit 1
fi
if [ "$ACTUAL_SHA256" != "$OPENSSL_SHA256" ]; then
print_error "SHA-256 mismatch for $TARBALL"
print_error " Expected: $OPENSSL_SHA256"
print_error " Actual: $ACTUAL_SHA256"
rm -f "$TARBALL"
exit 1
fi
print_success "SHA-256 verified"
tar xzf "$TARBALL"
cd "$PROJECT_ROOT"
fi
# Build for each target
for target_spec in "${TARGETS[@]}"; do
IFS=':' read -r rust_target openssl_target arch <<< "$target_spec"
build_openssl_for_target "$rust_target" "$openssl_target" "$arch"
done
print_success "All OpenSSL Android builds complete!"
echo "Install directory: $OPENSSL_INSTALL_DIR"
-3
View File
@@ -65,9 +65,6 @@ linker = "${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/${HOST_TAG}/bin/x86_64-li
CC_armv7_linux_androideabi = "${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/${HOST_TAG}/bin/armv7a-linux-androideabi33-clang"
CXX_armv7_linux_androideabi = "${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/${HOST_TAG}/bin/armv7a-linux-androideabi33-clang++"
AR_armv7_linux_androideabi = "${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/${HOST_TAG}/bin/llvm-ar"
OPENSSL_DIR = "${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/${HOST_TAG}/sysroot/usr"
OPENSSL_INCLUDE_DIR = "${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/${HOST_TAG}/sysroot/usr/include"
OPENSSL_LIB_DIR = "${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/${HOST_TAG}/sysroot/usr/lib/aarch64-linux-android"
EOF
print_success "Generated Cargo config for host: $HOST_TAG"