Add quickstart variant that launches with predefined credentials.

This commit is contained in:
Greyson Parrelli
2026-02-13 14:17:49 -05:00
committed by Alex Hart
parent 9922621945
commit e67307a961
7 changed files with 372 additions and 0 deletions

View File

@@ -65,6 +65,7 @@ import org.thoughtcrime.securesms.megaphone.Megaphones
import org.thoughtcrime.securesms.payments.DataExportUtil
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.registration.data.QuickstartCredentialExporter
import org.thoughtcrime.securesms.storage.StorageSyncHelper
import org.thoughtcrime.securesms.util.ConversationUtil
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
@@ -164,6 +165,16 @@ class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__inter
}
)
if (BuildConfig.DEBUG) {
clickPref(
title = DSLSettingsText.from("Export quickstart credentials"),
summary = DSLSettingsText.from("Export registration credentials to a JSON file for quickstart builds."),
onClick = {
exportQuickstartCredentials()
}
)
}
clickPref(
title = DSLSettingsText.from("Unregister"),
summary = DSLSettingsText.from("This will unregister your account without deleting it."),
@@ -1144,6 +1155,21 @@ class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__inter
}
}
private fun exportQuickstartCredentials() {
MaterialAlertDialogBuilder(requireContext())
.setTitle("Export quickstart credentials?")
.setMessage("This will export your account's private keys and credentials to an unencrypted file on disk. This is very dangerous! Only use it with test accounts.")
.setPositiveButton("Export") { _, _ ->
SimpleTask.run({
QuickstartCredentialExporter.export(requireContext())
}) { file ->
Toast.makeText(requireContext(), "Exported to ${file.absolutePath}", Toast.LENGTH_LONG).show()
}
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
private fun promptUserForSentTimestamp() {
val input = EditText(requireContext()).apply {
inputType = android.text.InputType.TYPE_CLASS_NUMBER

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.registration.data
import android.content.Context
import android.util.Base64
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.recipients.Recipient
import java.io.File
/**
* Exports current account registration credentials to a JSON file
* that can be used with quickstart builds.
*/
object QuickstartCredentialExporter {
private val TAG = Log.tag(QuickstartCredentialExporter::class.java)
private val json = Json { prettyPrint = true }
fun export(context: Context): File {
val aci = SignalStore.account.requireAci()
val pni = SignalStore.account.requirePni()
val e164 = SignalStore.account.requireE164()
val servicePassword = SignalStore.account.servicePassword ?: error("No service password")
val aciIdentityKeyPair = SignalStore.account.aciIdentityKey
val pniIdentityKeyPair = SignalStore.account.pniIdentityKey
val aciSignedPreKey = AppDependencies.protocolStore.aci().loadSignedPreKey(SignalStore.account.aciPreKeys.activeSignedPreKeyId)
val aciLastResortKyberPreKey = AppDependencies.protocolStore.aci().loadKyberPreKey(SignalStore.account.aciPreKeys.lastResortKyberPreKeyId)
val pniSignedPreKey = AppDependencies.protocolStore.pni().loadSignedPreKey(SignalStore.account.pniPreKeys.activeSignedPreKeyId)
val pniLastResortKyberPreKey = AppDependencies.protocolStore.pni().loadKyberPreKey(SignalStore.account.pniPreKeys.lastResortKyberPreKeyId)
val self = Recipient.self()
val profileKey = self.profileKey ?: error("No profile key")
val profileName = self.profileName
val credentials = QuickstartCredentials(
aci = aci.toString(),
pni = pni.toString(),
e164 = e164,
servicePassword = servicePassword,
aciIdentityKeyPair = Base64.encodeToString(aciIdentityKeyPair.serialize(), Base64.NO_WRAP),
pniIdentityKeyPair = Base64.encodeToString(pniIdentityKeyPair.serialize(), Base64.NO_WRAP),
aciSignedPreKey = Base64.encodeToString(aciSignedPreKey.serialize(), Base64.NO_WRAP),
aciLastResortKyberPreKey = Base64.encodeToString(aciLastResortKyberPreKey.serialize(), Base64.NO_WRAP),
pniSignedPreKey = Base64.encodeToString(pniSignedPreKey.serialize(), Base64.NO_WRAP),
pniLastResortKyberPreKey = Base64.encodeToString(pniLastResortKyberPreKey.serialize(), Base64.NO_WRAP),
profileKey = Base64.encodeToString(profileKey, Base64.NO_WRAP),
registrationId = SignalStore.account.registrationId,
pniRegistrationId = SignalStore.account.pniRegistrationId,
profileGivenName = profileName.givenName,
profileFamilyName = profileName.familyName
)
val outputDir = context.getExternalFilesDir(null) ?: error("No external files directory")
val outputFile = File(outputDir, "quickstart-credentials.json")
outputFile.writeText(json.encodeToString(credentials))
Log.i(TAG, "Exported quickstart credentials to ${outputFile.absolutePath}")
return outputFile
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.registration.data
import kotlinx.serialization.Serializable
/**
* JSON-serializable bundle of registration credentials for quickstart builds.
* All byte arrays are base64-encoded strings.
*/
@Serializable
data class QuickstartCredentials(
val version: Int = 1,
val aci: String,
val pni: String,
val e164: String,
val servicePassword: String,
val aciIdentityKeyPair: String,
val pniIdentityKeyPair: String,
val aciSignedPreKey: String,
val aciLastResortKyberPreKey: String,
val pniSignedPreKey: String,
val pniLastResortKyberPreKey: String,
val profileKey: String,
val registrationId: Int,
val pniRegistrationId: Int,
val profileGivenName: String,
val profileFamilyName: String
)