aboutsummaryrefslogtreecommitdiffstats
path: root/packages
diff options
context:
space:
mode:
Diffstat (limited to 'packages')
-rw-r--r--packages/meshbay-android/README.md18
-rw-r--r--packages/meshbay-android/app/build.gradle.kts29
-rw-r--r--packages/meshbay-hub/tests/test_android_shell.py11
3 files changed, 53 insertions, 5 deletions
diff --git a/packages/meshbay-android/README.md b/packages/meshbay-android/README.md
index bfd82cf..5cb474e 100644
--- a/packages/meshbay-android/README.md
+++ b/packages/meshbay-android/README.md
@@ -32,13 +32,29 @@ holds the same service and the same visibility, for as long as it plays
# needs JDK 17+ and an Android SDK (ANDROID_HOME, or sdk.dir in local.properties)
./gradlew assembleDebug # app/build/outputs/apk/debug/app-debug.apk
./gradlew testDebugUnitTest # JVM unit tests
+./gradlew assembleRelease # app/build/outputs/apk/release/app-release.apk
```
+A release is signed with the release key, which never enters the repository.
+`assembleRelease` reads it from `~/.gradle/gradle.properties`, and stops if
+any of these is missing rather than signing with the debug key:
+
+```properties
+meshbayReleaseStoreFile=/path/to/meshbay-release.jks
+meshbayReleaseStorePassword=...
+meshbayReleaseKeyAlias=meshbay
+meshbayReleaseKeyPassword=...
+```
+
+`apksigner verify --print-certs app-release.apk` prints the certificate's
+SHA-256 fingerprint, the one the download page publishes (§8.2). A release
+does not install over a debug build, or the reverse: the keys differ.
+
The security contract is also pinned from the Python suite by reading this
source: `packages/meshbay-hub/tests/test_android_shell.py`.
Not built yet: phone-specific behaviour (back button, network handover,
-keeping a download alive with the screen off), signed releases.
+keeping a download alive with the screen off), updates through a store.
## Icon
diff --git a/packages/meshbay-android/app/build.gradle.kts b/packages/meshbay-android/app/build.gradle.kts
index 3f29ac0..8afbc1a 100644
--- a/packages/meshbay-android/app/build.gradle.kts
+++ b/packages/meshbay-android/app/build.gradle.kts
@@ -8,6 +8,9 @@ val packageVersion = (JsonSlurper().parse(rootDir.resolve("../meshbay-client/pac
as Map<*, *>)["version"] as String
val versionParts = packageVersion.split(".").map { it.toInt() }
+val releaseSigning = listOf("meshbayReleaseStoreFile", "meshbayReleaseStorePassword",
+ "meshbayReleaseKeyAlias", "meshbayReleaseKeyPassword")
+
android {
namespace = "org.meshbay.client"
compileSdk = 37
@@ -18,13 +21,21 @@ android {
versionName = packageVersion
versionCode = versionParts[0] * 10000 + versionParts[1] * 100 + versionParts[2]
}
+ // The release key never enters the repository: its path and passwords come
+ // from ~/.gradle/gradle.properties. Without them a release build stops
+ // rather than signing with the debug key (see preReleaseBuild below).
+ if (releaseSigning.all { providers.gradleProperty(it).isPresent }) {
+ signingConfigs.create("release") {
+ storeFile = file(providers.gradleProperty("meshbayReleaseStoreFile").get())
+ storePassword = providers.gradleProperty("meshbayReleaseStorePassword").get()
+ keyAlias = providers.gradleProperty("meshbayReleaseKeyAlias").get()
+ keyPassword = providers.gradleProperty("meshbayReleaseKeyPassword").get()
+ }
+ }
buildTypes {
getByName("release") {
isMinifyEnabled = false
- // A stand-in until the release key exists (Stage D12): the debug
- // key, so a release build installs over a debug one and back
- // without losing the account. Not a key to publish anything with.
- signingConfig = signingConfigs.getByName("debug")
+ signingConfig = signingConfigs.findByName("release")
}
}
compileOptions {
@@ -81,6 +92,16 @@ val syncUi = tasks.register<SyncUi>("syncUi") {
outputDir.set(layout.buildDirectory.dir("generated/ui-assets"))
}
+// Checked when a release is built, not when the project is configured, so a
+// debug build and the unit tests need no key.
+tasks.configureEach {
+ if (name == "preReleaseBuild") doFirst {
+ val missing = releaseSigning.filter { !providers.gradleProperty(it).isPresent }
+ if (missing.isNotEmpty()) throw GradleException(
+ "no release key: set ${missing.joinToString()} in ~/.gradle/gradle.properties")
+ }
+}
+
androidComponents {
onVariants { variant ->
variant.sources.assets?.addGeneratedSourceDirectory(syncUi, SyncUi::outputDir)
diff --git a/packages/meshbay-hub/tests/test_android_shell.py b/packages/meshbay-hub/tests/test_android_shell.py
index e569bb7..71832f2 100644
--- a/packages/meshbay-hub/tests/test_android_shell.py
+++ b/packages/meshbay-hub/tests/test_android_shell.py
@@ -215,6 +215,17 @@ def test_the_version_is_the_packages_version():
assert not re.search(r'versionName = "\d', build)
+def test_a_release_is_signed_with_the_release_key_or_not_built():
+ """The key's path and passwords come from outside the repository, and a
+ release build without them stops instead of signing with the debug key."""
+ build = _strip_js_comments(_read(APP / "build.gradle.kts"))
+ assert 'signingConfigs.getByName("debug")' not in build
+ assert 'signingConfigs.findByName("release")' in build
+ assert 'providers.gradleProperty("meshbayReleaseStorePassword")' in build
+ assert '"preReleaseBuild"' in build and "throw GradleException" in build
+ assert not re.search(r'storePassword = "', build)
+
+
@pytest.mark.skipif(not os.environ.get("ANDROID_HOME") or shutil.which("java") is None,
reason="no Android SDK in the environment")
def test_the_jvm_unit_tests_pass():