mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-24 13:08:46 +00:00
Add a new debug option to the backup playground.
This commit is contained in:
@@ -90,6 +90,7 @@ class InternalBackupPlaygroundFragment : ComposeFragment() {
|
||||
private lateinit var exportFileLauncher: ActivityResultLauncher<Intent>
|
||||
private lateinit var importFileLauncher: ActivityResultLauncher<Intent>
|
||||
private lateinit var validateFileLauncher: ActivityResultLauncher<Intent>
|
||||
private lateinit var savePlaintextcopyLauncher: ActivityResultLauncher<Intent>
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -124,6 +125,15 @@ class InternalBackupPlaygroundFragment : ComposeFragment() {
|
||||
} ?: Toast.makeText(requireContext(), "No URI selected", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
savePlaintextcopyLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
if (result.resultCode == RESULT_OK) {
|
||||
result.data?.data?.let { uri ->
|
||||
viewModel.fetchRemoteBackupAndWritePlaintext(requireContext().contentResolver.openOutputStream(uri))
|
||||
Toast.makeText(requireContext(), "Check logs for progress.", Toast.LENGTH_SHORT).show()
|
||||
} ?: Toast.makeText(requireContext(), "No URI selected", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -188,7 +198,17 @@ class InternalBackupPlaygroundFragment : ComposeFragment() {
|
||||
.show()
|
||||
},
|
||||
onBackupTierSelected = { tier -> viewModel.onBackupTierSelected(tier) },
|
||||
onHaltAllJobs = { viewModel.haltAllJobs() }
|
||||
onHaltAllJobs = { viewModel.haltAllJobs() },
|
||||
onSavePlaintextCopy = {
|
||||
val intent = Intent().apply {
|
||||
action = Intent.ACTION_CREATE_DOCUMENT
|
||||
type = "application/octet-stream"
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
putExtra(Intent.EXTRA_TITLE, "backup-plaintext-${System.currentTimeMillis()}.binproto")
|
||||
}
|
||||
|
||||
savePlaintextcopyLauncher.launch(intent)
|
||||
}
|
||||
)
|
||||
},
|
||||
mediaContent = { snackbarHostState ->
|
||||
@@ -283,7 +303,8 @@ fun Screen(
|
||||
onTriggerBackupJobClicked: () -> Unit = {},
|
||||
onWipeDataAndRestoreClicked: () -> Unit = {},
|
||||
onBackupTierSelected: (MessageBackupTier?) -> Unit = {},
|
||||
onHaltAllJobs: () -> Unit = {}
|
||||
onHaltAllJobs: () -> Unit = {},
|
||||
onSavePlaintextCopy: () -> Unit = {}
|
||||
) {
|
||||
val scrollState = rememberScrollState()
|
||||
val options = remember {
|
||||
@@ -337,6 +358,12 @@ fun Screen(
|
||||
Text("Halt all backup jobs")
|
||||
}
|
||||
|
||||
Buttons.LargeTonal(
|
||||
onClick = onSavePlaintextCopy
|
||||
) {
|
||||
Text("Save plaintext copy of remote backup")
|
||||
}
|
||||
|
||||
Dividers.Default()
|
||||
|
||||
Row(
|
||||
|
||||
@@ -19,6 +19,10 @@ import io.reactivex.rxjava3.kotlin.plusAssign
|
||||
import io.reactivex.rxjava3.kotlin.subscribeBy
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.signal.core.util.concurrent.SignalExecutors
|
||||
import org.signal.core.util.copyTo
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.readNBytesOrThrow
|
||||
import org.signal.core.util.stream.LimitedInputStream
|
||||
import org.signal.libsignal.zkgroup.profiles.ProfileKey
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment
|
||||
@@ -30,6 +34,7 @@ import org.thoughtcrime.securesms.backup.v2.local.ArchiveResult
|
||||
import org.thoughtcrime.securesms.backup.v2.local.LocalArchiver
|
||||
import org.thoughtcrime.securesms.backup.v2.local.LocalArchiver.FailureCause
|
||||
import org.thoughtcrime.securesms.backup.v2.local.SnapshotFileSystem
|
||||
import org.thoughtcrime.securesms.backup.v2.stream.EncryptedBackupReader.Companion.MAC_SIZE
|
||||
import org.thoughtcrime.securesms.database.MessageType
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
@@ -45,16 +50,28 @@ import org.thoughtcrime.securesms.jobs.RestoreLocalAttachmentJob
|
||||
import org.thoughtcrime.securesms.jobs.SyncArchivedMediaJob
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.mms.IncomingMessage
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.whispersystems.signalservice.api.NetworkResult
|
||||
import org.whispersystems.signalservice.api.backup.MediaName
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.util.UUID
|
||||
import java.util.zip.GZIPInputStream
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.CipherInputStream
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
class InternalBackupPlaygroundViewModel : ViewModel() {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(InternalBackupPlaygroundViewModel::class)
|
||||
}
|
||||
|
||||
var backupData: ByteArray? = null
|
||||
|
||||
val disposables = CompositeDisposable()
|
||||
@@ -520,4 +537,39 @@ class InternalBackupPlaygroundViewModel : ViewModel() {
|
||||
AppDependencies.jobManager.cancelAllInQueue("ArchiveAttachmentJobs_1")
|
||||
AppDependencies.jobManager.cancelAllInQueue("ArchiveThumbnailUploadJob")
|
||||
}
|
||||
|
||||
fun fetchRemoteBackupAndWritePlaintext(outputStream: OutputStream?) {
|
||||
check(outputStream != null)
|
||||
|
||||
SignalExecutors.BOUNDED_IO.execute {
|
||||
Log.d(TAG, "Downloading file...")
|
||||
val tempBackupFile = BlobProvider.getInstance().forNonAutoEncryptingSingleSessionOnDisk(AppDependencies.application)
|
||||
if (!BackupRepository.downloadBackupFile(tempBackupFile)) {
|
||||
Log.e(TAG, "Failed to download backup file")
|
||||
throw IOException()
|
||||
}
|
||||
|
||||
val encryptedStream = tempBackupFile.inputStream()
|
||||
val iv = encryptedStream.readNBytesOrThrow(16)
|
||||
val backupKey = SignalStore.svr.orCreateMasterKey.deriveBackupKey()
|
||||
val keyMaterial = backupKey.deriveBackupSecrets(Recipient.self().aci.get())
|
||||
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding").apply {
|
||||
init(Cipher.DECRYPT_MODE, SecretKeySpec(keyMaterial.cipherKey, "AES"), IvParameterSpec(iv))
|
||||
}
|
||||
|
||||
val plaintextStream = GZIPInputStream(
|
||||
CipherInputStream(
|
||||
LimitedInputStream(
|
||||
wrapped = encryptedStream,
|
||||
maxBytes = tempBackupFile.length() - MAC_SIZE
|
||||
),
|
||||
cipher
|
||||
)
|
||||
)
|
||||
|
||||
Log.d(TAG, "Copying...")
|
||||
plaintextStream.copyTo(outputStream)
|
||||
Log.d(TAG, "Done!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user