174 lines
7.6 KiB
Markdown
174 lines
7.6 KiB
Markdown
|
|
# 08. Воспроизводимая сборка — Montana Android v6.5.0
|
|||
|
|
|
|||
|
|
## §1. Сборочное окружение
|
|||
|
|
|
|||
|
|
| Компонент | Версия | Где |
|
|||
|
|
|-----------|--------|-----|
|
|||
|
|
| macOS | 14.x / 15.x (Sonoma / Sequoia) | сборка |
|
|||
|
|
| Homebrew | актуальная | `/opt/homebrew` |
|
|||
|
|
| OpenJDK | 17 | `/opt/homebrew/opt/openjdk@17` |
|
|||
|
|
| Android SDK | platform-tools + build-tools 34.0.0 | `/opt/homebrew/share/android-commandlinetools` |
|
|||
|
|
| Gradle | через `./gradlew` (8.x) | wrapper в проекте |
|
|||
|
|
| Android Gradle Plugin | 8.x | `app/build.gradle.kts` |
|
|||
|
|
| Kotlin | 2.0+ | через AGP |
|
|||
|
|
| `apksigner` | 34.0.0 | Android SDK build-tools |
|
|||
|
|
| `zipalign` | 34.0.0 | Android SDK build-tools |
|
|||
|
|
|
|||
|
|
## §2. Зависимости
|
|||
|
|
|
|||
|
|
`app/build.gradle.kts`:
|
|||
|
|
```kotlin
|
|||
|
|
dependencies {
|
|||
|
|
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar","*.jar"))))
|
|||
|
|
implementation("androidx.core:core-ktx:1.13.1")
|
|||
|
|
implementation("androidx.appcompat:appcompat:1.7.0")
|
|||
|
|
implementation("androidx.webkit:webkit:1.11.0")
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`app/libs/`:
|
|||
|
|
- `libv2ray-v2tun.jar` — xray-core bindings (libgojni.so wrapper)
|
|||
|
|
|
|||
|
|
`app/src/main/jniLibs/`:
|
|||
|
|
- `arm64-v8a/libgojni.so` — xray-core native (Go)
|
|||
|
|
- `arm64-v8a/libhev-socks5-tunnel.so` — TUN ↔ SOCKS5 bridge
|
|||
|
|
- `arm64-v8a/libhysteria2.so` — legacy artifact (не используется в v6.5.0, но included)
|
|||
|
|
- `armeabi-v7a/libhev-socks5-tunnel.so`
|
|||
|
|
- `x86/libhev-socks5-tunnel.so`
|
|||
|
|
- `x86_64/libhev-socks5-tunnel.so`
|
|||
|
|
|
|||
|
|
**Происхождение native libs:** извлечены из APK [v2rayNG](https://github.com/2dust/v2rayNG) v8.0.x (форк xray-core под Android). Лицензия GPL-3.0. SHA-256 verification см. §6.
|
|||
|
|
|
|||
|
|
## §3. Assets
|
|||
|
|
|
|||
|
|
`app/src/main/assets/`:
|
|||
|
|
- `app.html` — single-page приложение (HTML + CSS + JS), включая монетные изображения base64
|
|||
|
|
- `bip39-en.txt` — BIP39 EN wordlist (2048 слов, 13116 байт, SHA-256 = `2f5eed53a4727b4bf8880d8f3f199efc90e58503646d9ff8eff3a2ed3b24dbda`)
|
|||
|
|
- `geoip.dat` — MaxMind country DB for xray geoip routing
|
|||
|
|
- `geosite.dat` — MaxMind site categories DB for xray geosite routing
|
|||
|
|
- `juno.jpg`, `juno-back.jpg` — изображения монеты Юноны (front / back)
|
|||
|
|
- `symbol.jpg` — символ времени (логотип Genesis)
|
|||
|
|
|
|||
|
|
Все assets копируются в APK как есть (без сжатия для JPEG).
|
|||
|
|
|
|||
|
|
## §4. Полная последовательность сборки
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cd /Users/kh./Python/Ничто/Montana/Android/MontanaApp
|
|||
|
|
|
|||
|
|
# 1. Bump version в build.gradle.kts вручную либо через sed
|
|||
|
|
# versionCode = NNNNN
|
|||
|
|
# versionName = "X.Y.Z"
|
|||
|
|
|
|||
|
|
# 2. Сборка debug APK через Gradle
|
|||
|
|
export JAVA_HOME=/opt/homebrew/opt/openjdk@17
|
|||
|
|
export PATH="$JAVA_HOME/bin:$PATH"
|
|||
|
|
./gradlew assembleDebug
|
|||
|
|
|
|||
|
|
# Результат: app/build/outputs/apk/debug/app-debug.apk (~32 MB)
|
|||
|
|
# Эта APK подписана debug-keystore Android SDK — пригодна для тестирования,
|
|||
|
|
# но НЕ для production (другой SHA-256 fingerprint)
|
|||
|
|
|
|||
|
|
# 3. Production sign через Genesis keystore
|
|||
|
|
SDK=/opt/homebrew/share/android-commandlinetools
|
|||
|
|
APKSIGNER=$(ls $SDK/build-tools/*/apksigner | tail -1)
|
|||
|
|
ZIPALIGN=$(ls $SDK/build-tools/*/zipalign | tail -1)
|
|||
|
|
KEYPASS=$(cat /Users/kh./Python/Ничто/Montana/Android/keystore/.password)
|
|||
|
|
|
|||
|
|
# 3a. zipalign для оптимизации
|
|||
|
|
"$ZIPALIGN" -p -f 4 \
|
|||
|
|
app/build/outputs/apk/debug/app-debug.apk \
|
|||
|
|
/tmp/montana-aligned.apk
|
|||
|
|
|
|||
|
|
# 3b. Sign с Genesis keystore
|
|||
|
|
"$APKSIGNER" sign \
|
|||
|
|
--ks /Users/kh./Python/Ничто/Montana/Android/keystore/montana.keystore \
|
|||
|
|
--ks-pass "pass:$KEYPASS" \
|
|||
|
|
--key-pass "pass:$KEYPASS" \
|
|||
|
|
--ks-key-alias montana \
|
|||
|
|
--out /Users/kh./Python/Ничто/Montana/Android/build/montana-X.Y.Z.apk \
|
|||
|
|
/tmp/montana-aligned.apk
|
|||
|
|
|
|||
|
|
# 4. Verification
|
|||
|
|
"$APKSIGNER" verify --print-certs /Users/kh./Python/Ничто/Montana/Android/build/montana-X.Y.Z.apk
|
|||
|
|
# Ожидаемый SHA-256 fingerprint: 305bc99b40e6106f28c6fcc5dce4772761d2630d5aca9fee076dc0691913ce4d
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## §5. Reproducibility audit
|
|||
|
|
|
|||
|
|
**Заявление:** при идентичном source + identical toolchain + identical timestamps две сборки должны давать **byte-identical** APK.
|
|||
|
|
|
|||
|
|
**Реальность:** Android Gradle Plugin **по умолчанию НЕ воспроизводим** из-за:
|
|||
|
|
- Время компиляции в `META-INF/MANIFEST.MF`
|
|||
|
|
- Random salt в dex builder (можно отключить через `android.experimental.legacyTransform.forceNonIncremental=true`)
|
|||
|
|
- Порядок entries в ZIP
|
|||
|
|
|
|||
|
|
**Текущий status:** v6.5.0 НЕ reproducible byte-exact. Two consecutive builds могут отличаться на ~1-2% (timestamps).
|
|||
|
|
|
|||
|
|
**Closure path:**
|
|||
|
|
1. Enable `android.useAndroidX=true` + Reproducible APK Builder plugin
|
|||
|
|
2. Set `SOURCE_DATE_EPOCH` env var
|
|||
|
|
3. Strip timestamps from MANIFEST.MF
|
|||
|
|
|
|||
|
|
**Cost estimate:** 1 день. Не блокер для текущего release, но обязательно для security-critical распространения.
|
|||
|
|
|
|||
|
|
## §6. Native libraries verification
|
|||
|
|
|
|||
|
|
**Способ:** SHA-256 каждой `.so` файла фиксирован, в commit history. При сборке текущей версии auditor сравнивает:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cd /Users/kh./Python/Ничто/Montana/Android/MontanaApp/app/src/main/jniLibs
|
|||
|
|
sha256sum arm64-v8a/*.so armeabi-v7a/*.so x86/*.so x86_64/*.so
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Ожидаемые значения (приведены в `приложения/нативные-библиотеки.md` после первого release-tag).
|
|||
|
|
|
|||
|
|
**Слабость:** если злоумышленник подменил `.so` файлы при инициальном извлечении из v2rayNG → SHA-256 в commit history тоже malicious. Mitigation: cross-check с upstream v2rayNG release SHA-256 (опубликованы на GitHub releases).
|
|||
|
|
|
|||
|
|
## §7. APK signature verification независимым аудитором
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# Скачать APK
|
|||
|
|
curl -O https://montana.quest/vpn/montana-v6.5.0.apk
|
|||
|
|
|
|||
|
|
# Verify signature
|
|||
|
|
apksigner verify --print-certs montana-v6.5.0.apk
|
|||
|
|
|
|||
|
|
# Ожидаемый output:
|
|||
|
|
# Signer #1 certificate DN: CN=Montana VPN, O=Montana Network, L=Genesis, C=RU
|
|||
|
|
# Signer #1 certificate SHA-256 digest: 305bc99b40e6106f28c6fcc5dce4772761d2630d5aca9fee076dc0691913ce4d
|
|||
|
|
# Signer #1 certificate SHA-1 digest: a0be58ad4d9c353eb954daf530d808ff661d9a01
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Несовпадение SHA-256 fingerprint = либо APK не подписана Genesis keystore (= malicious), либо keystore был ротирован (= анонсировано отдельно в `Montana/Russian/Genesis/`).
|
|||
|
|
|
|||
|
|
## §8. Сборка с нуля
|
|||
|
|
|
|||
|
|
Для аудитора который хочет собрать APK с нуля:
|
|||
|
|
|
|||
|
|
1. Clone репозитория Montana:
|
|||
|
|
```bash
|
|||
|
|
git clone https://github.com/efir369999/Montana-App.git
|
|||
|
|
cd Montana-App
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
2. Установить toolchain:
|
|||
|
|
```bash
|
|||
|
|
brew install openjdk@17
|
|||
|
|
brew install --cask android-commandlinetools
|
|||
|
|
sdkmanager "platforms;android-34" "build-tools;34.0.0"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
3. Прогнать сборку:
|
|||
|
|
```bash
|
|||
|
|
cd Montana/Android/MontanaApp
|
|||
|
|
./gradlew assembleDebug
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
4. Сравнить полученный `app-debug.apk` (debug-signed) с published `montana-v6.5.0.apk` (Genesis-signed):
|
|||
|
|
- Unzip обе APK
|
|||
|
|
- Сравнить `classes.dex`, `resources.arsc`, `AndroidManifest.xml`, native `.so`
|
|||
|
|
- Должны быть идентичны (cryptographically — диff только в META-INF/CERT.SF и META-INF/CERT.RSA)
|
|||
|
|
|
|||
|
|
**Closure path к full reproducibility:** §5.
|