diff options
Diffstat (limited to 'packages')
| -rw-r--r-- | packages/meshbay-android/README.md | 18 | ||||
| -rw-r--r-- | packages/meshbay-android/app/build.gradle.kts | 29 | ||||
| -rw-r--r-- | packages/meshbay-hub/tests/test_android_shell.py | 11 |
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(): |