mirror of
https://github.com/Monkopedia/rouse-context.git
synced 2026-10-19 10:33:18 +00:00
Fix #205: move release keystore passwords to env/.properties file
Release keystore passwords were hardcoded as string literals in `app/build.gradle.kts`, public to anyone who forks or clones the repo. Combined with a leaked keystore file (from a backup or CI compromise), that collapses two factors into one and lets an attacker sign arbitrary APKs as `com.rousecontext`. Release passwords are now resolved from, in order of precedence: 1. `ROUSE_RELEASE_STORE_PASSWORD` / `ROUSE_RELEASE_KEY_PASSWORD` env vars 2. `storePassword` / `keyPassword` keys in `.signing/release.properties` Both are absent from the tracked repo. A new `.signing/release.properties.example` template is committed so new clones see the expected format, and `.gitignore` now allows that one file while still ignoring the real `release.properties`, keystores, and any other signing artifacts. Resolution is lazy: non-release tasks (lint, unit tests, debug builds) do not require the env var or .properties file. A `taskGraph.whenReady` check produces a clear error message when a release-signing task is actually requested without credentials, instead of failing obscurely inside the Android signing task. Debug keystore password stays hardcoded on purpose — the debug key is not a secret. Docs updated in `docs/self-hosting.md`. Keystore files themselves are untouched (regenerating would force a user uninstall and burn ACME cert quota).
This commit is contained in:
parent
05775a0d33
commit
f439d5f5fc
4 changed files with 80 additions and 5 deletions
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -11,5 +11,7 @@ local.properties
|
|||
# google-services.json is checked in — API keys are not secret (embedded in APK)
|
||||
relay/target/
|
||||
.claude/worktrees/
|
||||
.signing/
|
||||
.signing
|
||||
# Signing keystores and password files are private per clone.
|
||||
# Only the *.example templates are tracked.
|
||||
.signing/*
|
||||
!.signing/release.properties.example
|
||||
|
|
|
|||
10
.signing/release.properties.example
Normal file
10
.signing/release.properties.example
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# Template for .signing/release.properties — copy this file to
|
||||
# .signing/release.properties and fill in the real passwords locally.
|
||||
#
|
||||
# The real file is gitignored (see .gitignore). This .example file is
|
||||
# tracked so new clones know the expected format.
|
||||
#
|
||||
# Alternative: set ROUSE_RELEASE_STORE_PASSWORD and ROUSE_RELEASE_KEY_PASSWORD
|
||||
# environment variables instead. Env vars take precedence over this file.
|
||||
storePassword=your-release-store-password
|
||||
keyPassword=your-release-key-password
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
import java.util.Properties
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
|
|
@ -7,16 +9,36 @@ plugins {
|
|||
alias(libs.plugins.google.services)
|
||||
}
|
||||
|
||||
// Loads a value from `.signing/release.properties` if that file exists.
|
||||
// The file is gitignored; see `.signing/release.properties.example` for format.
|
||||
val releaseSigningProps: Properties? = run {
|
||||
val f = rootProject.file(".signing/release.properties")
|
||||
if (f.exists()) {
|
||||
Properties().apply { f.inputStream().use { load(it) } }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun loadFromSigningProperties(key: String): String? = releaseSigningProps?.getProperty(key)
|
||||
|
||||
android {
|
||||
namespace = "com.rousecontext.app"
|
||||
compileSdk = 36
|
||||
|
||||
// Release passwords are resolved lazily so non-release tasks (lint, tests,
|
||||
// debug builds) don't require the env vars or .properties file.
|
||||
val releaseStorePassword = System.getenv("ROUSE_RELEASE_STORE_PASSWORD")
|
||||
?: loadFromSigningProperties("storePassword")
|
||||
val releaseKeyPassword = System.getenv("ROUSE_RELEASE_KEY_PASSWORD")
|
||||
?: loadFromSigningProperties("keyPassword")
|
||||
|
||||
signingConfigs {
|
||||
create("release") {
|
||||
storeFile = file("${rootProject.projectDir}/.signing/release.keystore")
|
||||
storePassword = "rousecontext-release"
|
||||
storePassword = releaseStorePassword
|
||||
keyAlias = "release"
|
||||
keyPassword = "rousecontext-release"
|
||||
keyPassword = releaseKeyPassword
|
||||
}
|
||||
getByName("debug") {
|
||||
storeFile = file("${rootProject.projectDir}/.signing/debug.keystore")
|
||||
|
|
@ -26,6 +48,28 @@ android {
|
|||
}
|
||||
}
|
||||
|
||||
// Fail fast on release builds if passwords are missing, with a clear message.
|
||||
gradle.taskGraph.whenReady {
|
||||
val needsReleaseSigning = allTasks.any { task ->
|
||||
if (task.project != project || !task.name.contains("Release")) return@any false
|
||||
task.name.startsWith("package") ||
|
||||
task.name.startsWith("assemble") ||
|
||||
task.name.startsWith("bundle")
|
||||
}
|
||||
if (needsReleaseSigning) {
|
||||
check(releaseStorePassword != null) {
|
||||
"Release signing requires ROUSE_RELEASE_STORE_PASSWORD env var " +
|
||||
"or storePassword in .signing/release.properties " +
|
||||
"(see .signing/release.properties.example)."
|
||||
}
|
||||
check(releaseKeyPassword != null) {
|
||||
"Release signing requires ROUSE_RELEASE_KEY_PASSWORD env var " +
|
||||
"or keyPassword in .signing/release.properties " +
|
||||
"(see .signing/release.properties.example)."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.rousecontext"
|
||||
minSdk = 24
|
||||
|
|
|
|||
|
|
@ -63,7 +63,26 @@ keytool -genkeypair -v \
|
|||
-dname "CN=Release, O=YourOrg, C=US"
|
||||
```
|
||||
|
||||
Update `app/build.gradle.kts` signing config blocks to match your passwords, or (better) read them from environment variables.
|
||||
Release passwords are read at build time from either environment variables or
|
||||
a local properties file — they are **not** committed to the repo. Choose one:
|
||||
|
||||
Environment variables (preferred for CI):
|
||||
|
||||
```bash
|
||||
export ROUSE_RELEASE_STORE_PASSWORD=YOUR_RELEASE_PASS
|
||||
export ROUSE_RELEASE_KEY_PASSWORD=YOUR_RELEASE_PASS
|
||||
```
|
||||
|
||||
Or a local properties file (preferred for interactive dev — survives shells):
|
||||
|
||||
```bash
|
||||
cp .signing/release.properties.example .signing/release.properties
|
||||
# edit .signing/release.properties and set storePassword / keyPassword
|
||||
```
|
||||
|
||||
`.signing/release.properties` is gitignored. Env vars take precedence over the
|
||||
file. Debug signing still uses a fixed password in `app/build.gradle.kts`
|
||||
because the debug keystore is not a secret.
|
||||
|
||||
**Back these up off-machine.** Regenerating a keystore forces users to uninstall (app-data loss) and burns ACME cert quota on re-registration.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue