mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-09-19 04:59:59 +00:00
Merge 0394e5cdc7c1fc3d93a66470ad379361afbb4b55 into c127eb83ab94c069c32d37530d2faecd381cd2a8
This commit is contained in:
commit
17d5a81c98
@ -1,5 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Auto Backup rules for API 30 and below. android:allowBackup="false" already disables backup
|
||||
on these releases, so this file is a second line of defence rather than the primary one — but
|
||||
it must not disagree with data_extraction_rules.xml about what is sensitive.
|
||||
|
||||
Deny-by-default for the same reason: the previous list named "bitchat_crypto.xml", which
|
||||
EncryptionService stopped using when it moved to "bitchat_crypto_secure".
|
||||
-->
|
||||
<full-backup-content>
|
||||
<exclude domain="sharedpref" path="bitchat_prefs.xml"/>
|
||||
<exclude domain="sharedpref" path="bitchat_crypto.xml"/>
|
||||
<exclude domain="root" path="." />
|
||||
<exclude domain="file" path="." />
|
||||
<exclude domain="database" path="." />
|
||||
<exclude domain="sharedpref" path="." />
|
||||
<exclude domain="external" path="." />
|
||||
<exclude domain="device_root" path="." />
|
||||
<exclude domain="device_file" path="." />
|
||||
<exclude domain="device_database" path="." />
|
||||
<exclude domain="device_sharedpref" path="." />
|
||||
</full-backup-content>
|
||||
|
||||
@ -1,11 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
None of bitchat's app-private storage may leave the device.
|
||||
|
||||
These rules are deny-by-default on purpose. An allow-by-default list that names individual
|
||||
files goes stale the moment storage is renamed or added, which is how "bitchat_crypto.xml"
|
||||
came to be excluded here after EncryptionService had already moved to
|
||||
"bitchat_crypto_secure", leaving the live key store, "bitchat_identity", and
|
||||
"private_conversations.db" eligible.
|
||||
|
||||
android:allowBackup="false" covers cloud backup, but for apps targeting Android 12 (API 31)
|
||||
or higher the platform documents that device-to-device migration cannot be disabled on some
|
||||
manufacturers' devices, so <device-transfer> is the only thing between a phone migration and
|
||||
a copy of the identity keys and every private message.
|
||||
-->
|
||||
<data-extraction-rules>
|
||||
<cloud-backup>
|
||||
<exclude domain="sharedpref" path="bitchat_prefs.xml"/>
|
||||
<exclude domain="sharedpref" path="bitchat_crypto.xml"/>
|
||||
<exclude domain="root" path="." />
|
||||
<exclude domain="file" path="." />
|
||||
<exclude domain="database" path="." />
|
||||
<exclude domain="sharedpref" path="." />
|
||||
<exclude domain="external" path="." />
|
||||
<exclude domain="device_root" path="." />
|
||||
<exclude domain="device_file" path="." />
|
||||
<exclude domain="device_database" path="." />
|
||||
<exclude domain="device_sharedpref" path="." />
|
||||
</cloud-backup>
|
||||
<device-transfer>
|
||||
<exclude domain="sharedpref" path="bitchat_prefs.xml"/>
|
||||
<exclude domain="sharedpref" path="bitchat_crypto.xml"/>
|
||||
<exclude domain="root" path="." />
|
||||
<exclude domain="file" path="." />
|
||||
<exclude domain="database" path="." />
|
||||
<exclude domain="sharedpref" path="." />
|
||||
<exclude domain="external" path="." />
|
||||
<exclude domain="device_root" path="." />
|
||||
<exclude domain="device_file" path="." />
|
||||
<exclude domain="device_database" path="." />
|
||||
<exclude domain="device_sharedpref" path="." />
|
||||
</device-transfer>
|
||||
</data-extraction-rules>
|
||||
|
||||
@ -0,0 +1,97 @@
|
||||
package com.bitchat.android
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* bitchat's app-private storage — the Noise/Nostr key material, the identity store and the
|
||||
* private conversation database — must never be handed to Auto Backup or to a device-to-device
|
||||
* transfer.
|
||||
*
|
||||
* These rules used to be an allow-by-default list naming two files, and it went stale: it
|
||||
* excluded "bitchat_crypto" after EncryptionService had already moved to
|
||||
* "bitchat_crypto_secure". So the invariant pinned here is deny-by-default over whole domains,
|
||||
* which cannot go stale when storage is renamed or added.
|
||||
*/
|
||||
class BackupExclusionContractTest {
|
||||
|
||||
private val resourcesDirectory = File("src/main/res")
|
||||
private val manifest = File("src/main/AndroidManifest.xml")
|
||||
|
||||
private val allDomains = setOf(
|
||||
"root",
|
||||
"file",
|
||||
"database",
|
||||
"sharedpref",
|
||||
"external",
|
||||
"device_root",
|
||||
"device_file",
|
||||
"device_database",
|
||||
"device_sharedpref"
|
||||
)
|
||||
|
||||
private fun rulesFile(name: String) = File(resourcesDirectory, "xml/$name").readText()
|
||||
|
||||
/** Domains excluded at path "." within [section], or across the file when it has no sections. */
|
||||
private fun excludedDomains(xml: String, section: String?): Set<String> {
|
||||
val scope = if (section == null) {
|
||||
xml
|
||||
} else {
|
||||
Regex("""<$section[^>]*>(.*?)</$section>""", RegexOption.DOT_MATCHES_ALL)
|
||||
.find(xml)
|
||||
?.groupValues
|
||||
?.get(1)
|
||||
?: fail("missing <$section> section")
|
||||
}
|
||||
return Regex("""<exclude\s+domain="([^"]+)"\s+path="\."\s*/>""")
|
||||
.findAll(scope)
|
||||
.map { it.groupValues[1] }
|
||||
.toSet()
|
||||
}
|
||||
|
||||
private fun fail(message: String): Nothing = throw AssertionError(message)
|
||||
|
||||
@Test
|
||||
fun `cloud backup excludes every storage domain`() {
|
||||
assertEquals(
|
||||
allDomains,
|
||||
excludedDomains(rulesFile("data_extraction_rules.xml"), "cloud-backup")
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `device to device transfer excludes every storage domain`() {
|
||||
// allowBackup=false does not reliably disable D2D migration on Android 12+, so this
|
||||
// section is the only thing protecting a phone migration.
|
||||
assertEquals(
|
||||
allDomains,
|
||||
excludedDomains(rulesFile("data_extraction_rules.xml"), "device-transfer")
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `legacy auto backup rules exclude every storage domain`() {
|
||||
assertEquals(allDomains, excludedDomains(rulesFile("backup_rules.xml"), null))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `no rule opts any path back in`() {
|
||||
for (name in listOf("data_extraction_rules.xml", "backup_rules.xml")) {
|
||||
assertFalse(
|
||||
"$name must not re-include anything",
|
||||
rulesFile(name).contains("<include")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `the manifest still disables backup and points at both rule files`() {
|
||||
val text = manifest.readText()
|
||||
assertTrue(text.contains("""android:allowBackup="false""""))
|
||||
assertTrue(text.contains("""android:dataExtractionRules="@xml/data_extraction_rules""""))
|
||||
assertTrue(text.contains("""android:fullBackupContent="@xml/backup_rules""""))
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user