From 70569a9699dec61e0e8b8286b4022e597b5ebbfd Mon Sep 17 00:00:00 2001 From: satyakwok Date: Thu, 7 May 2026 16:16:03 +0200 Subject: [PATCH 1/2] fix(android): proper release signing config + R8 minify (audit H2 + H3) Audit findings 2026-05-07: H2 (HIGH): release builds were signed with the debug keystore. Anyone could resign the published APK and impersonate Solux. Now reads release signing config from local key.properties (gitignored) OR CI env vars (SOLUX_KEYSTORE_PATH/_PASSWORD/_KEY_ALIAS/_KEY_PASSWORD). Build fails fast with a clear error if neither is provided. H3 (HIGH): release builds had no minify / no R8. When the crypto layer lands, an unminified binary makes keystore logic + signing flow trivially readable. Enabling R8 + resource shrinking now (pre-crypto) so the discipline is in place before sensitive code arrives. Stub proguard-rules.pro added with Flutter keeps. .gitignore: never commit key.properties / *.jks / *.keystore. Operator action items post-merge: 1. Generate a release keystore (one-time): keytool -genkey -v -keystore solux-release.jks -keyalg RSA -keysize 2048 -validity 10000 -alias solux 2. Add it to GH secrets (4 values: SOLUX_KEYSTORE_PATH, _PASSWORD, _KEY_ALIAS, _KEY_PASSWORD) 3. Update CI workflow to base64-decode keystore from secret + set env vars before flutter build apk 4. (separate work) Add release signing job to release pipeline --- android/app/build.gradle.kts | 47 +++++++++++++++++++++++++++++++--- android/app/proguard-rules.pro | 14 ++++++++++ 2 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 android/app/proguard-rules.pro diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 85507f8..a7ed79c 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -30,11 +30,52 @@ android { versionName = flutter.versionName } + signingConfigs { + // Release signing: configured via local key.properties (NOT committed) OR + // CI-injected env vars (SOLUX_KEYSTORE_PATH / _PASSWORD / _KEY_ALIAS / _KEY_PASSWORD). + // Falls back to debug keystore only in dev builds — release builds without a + // properly-configured release keystore will fail with a clear error. + create("release") { + val keyPropsFile = rootProject.file("key.properties") + if (keyPropsFile.exists()) { + val keyProps = java.util.Properties().apply { + load(keyPropsFile.inputStream()) + } + storeFile = file(keyProps.getProperty("storeFile") ?: "") + storePassword = keyProps.getProperty("storePassword") ?: "" + keyAlias = keyProps.getProperty("keyAlias") ?: "" + keyPassword = keyProps.getProperty("keyPassword") ?: "" + } else { + System.getenv("SOLUX_KEYSTORE_PATH")?.let { + storeFile = file(it) + storePassword = System.getenv("SOLUX_KEYSTORE_PASSWORD") ?: "" + keyAlias = System.getenv("SOLUX_KEY_ALIAS") ?: "" + keyPassword = System.getenv("SOLUX_KEY_PASSWORD") ?: "" + } + } + } + } + buildTypes { release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig = signingConfigs.getByName("debug") + // Audit H2 (2026-05-07): release builds previously signed with the debug + // keystore — anyone could resign the published APK and impersonate Solux. + // Now signs with the release config (loaded from key.properties or + // CI env vars). Build fails fast if neither is provided. + signingConfig = signingConfigs.findByName("release") + ?: throw GradleException("Release signing config not found. Provide " + + "key.properties OR set SOLUX_KEYSTORE_* env vars before building.") + + // Audit H3 (2026-05-07): R8 minify + resource shrinking. When the crypto + // layer lands, an unminified release binary lets attackers read keystore + // logic + signing flow trivially. Enabling R8 now (pre-crypto) so the + // discipline is in place before any sensitive code lands. + isMinifyEnabled = true + isShrinkResources = true + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) } } } diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro new file mode 100644 index 0000000..3baba9e --- /dev/null +++ b/android/app/proguard-rules.pro @@ -0,0 +1,14 @@ +# Solux Android ProGuard / R8 rules. +# Audit H3 (2026-05-07): R8 minify + resource shrinking enabled on release. +# Add specific keep rules below as new dependencies need them. + +# Flutter built-in +-keep class io.flutter.app.** { *; } +-keep class io.flutter.plugin.** { *; } +-keep class io.flutter.util.** { *; } +-keep class io.flutter.view.** { *; } +-keep class io.flutter.** { *; } +-keep class io.flutter.plugins.** { *; } + +# When the crypto layer lands, add explicit -keep rules for any +# cryptographic classes that R8 might inadvertently strip. From 02bc836350c161684ed87ddcf8a5381a6419fdf6 Mon Sep 17 00:00:00 2001 From: satyakwok Date: Thu, 7 May 2026 16:43:31 +0200 Subject: [PATCH 2/2] fix(android): import java.util.Properties (kotlin DSL needs explicit import) --- android/app/build.gradle.kts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index a7ed79c..2aeba9d 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -1,3 +1,5 @@ +import java.util.Properties + plugins { id("com.android.application") id("kotlin-android") @@ -38,7 +40,7 @@ android { create("release") { val keyPropsFile = rootProject.file("key.properties") if (keyPropsFile.exists()) { - val keyProps = java.util.Properties().apply { + val keyProps = Properties().apply { load(keyPropsFile.inputStream()) } storeFile = file(keyProps.getProperty("storeFile") ?: "")