Skip to content

Commit

Permalink
Implement export of single PIN
Browse files Browse the repository at this point in the history
  • Loading branch information
cyb3rko committed Feb 23, 2023
1 parent c1bfcb6 commit c0bfca1
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ import kotlin.experimental.and

internal object CryptoManager {
const val PIN_CRYPTO_ITERATION = 0
const val BACKUP_CRYPTO_ITERATION = 0
const val SINGLE_BACKUP_CRYPTO_ITERATION = 0
const val MULTI_BACKUP_CRYPTO_ITERATION = 0
const val PINS_FILE = "pins"
private const val KEYSTORE_ALIAS = "iamsecure"
private const val ENC_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ class HomeFragment : Fragment() {
registerForActivityResult(StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val uri = result.data?.data ?: return@registerForActivityResult
BackupHandler.runBackup(myContext, uri)
BackupHandler.runBackup(myContext, uri, true)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ import com.cyb3rko.pincredible.data.PinTable
import com.cyb3rko.pincredible.databinding.FragmentPinViewerBinding
import com.cyb3rko.pincredible.modals.AcceptDialog
import com.cyb3rko.pincredible.modals.ErrorDialog
import com.cyb3rko.pincredible.utils.BackupHandler
import com.cyb3rko.pincredible.utils.BackupHandler.SingleBackupStructure
import com.cyb3rko.pincredible.utils.ObjectSerializer
import com.cyb3rko.pincredible.utils.TableScreenshotHandler
import com.cyb3rko.pincredible.utils.Vibration
Expand All @@ -58,6 +60,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
import kotlin.properties.Delegates

class PinViewerFragment : Fragment() {
private var _binding: FragmentPinViewerBinding? = null
Expand All @@ -69,8 +72,10 @@ class PinViewerFragment : Fragment() {
private val vibrator by lazy { Vibration.getVibrator(myContext) }
private val args: PinViewerFragmentArgs by navArgs()
private val hash by lazy { CryptoManager.xxHash(args.pin) }
private lateinit var pinTable: PinTable
private var siid by Delegates.notNull<Byte>()

private val fileCreatorResultLauncher =
private val imageCreatorResultLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val uri = result.data?.data ?: return@registerForActivityResult
Expand All @@ -90,6 +95,15 @@ class PinViewerFragment : Fragment() {
}
}

private val fileCreatorResultLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val uri = result.data?.data ?: return@registerForActivityResult
val singleBackup = SingleBackupStructure(pinTable, siid, args.pin)
BackupHandler.runBackup(myContext, uri, false, singleBackup)
}
}

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
Expand All @@ -112,7 +126,7 @@ class PinViewerFragment : Fragment() {

binding.saveImageButton.setOnClickListener {
TableScreenshotHandler.initiateTableImage(
fileCreatorResultLauncher,
imageCreatorResultLauncher,
"${args.pin}.jpg"
)
}
Expand Down Expand Up @@ -149,16 +163,21 @@ class PinViewerFragment : Fragment() {
.setNegativeButton(R.string.dialog_delete_button2, null)
.show()
}

binding.exportFab.setOnClickListener {
BackupHandler.initiateSingleBackup(hash, fileCreatorResultLauncher)
}
}

private suspend fun loadDataIntoTable() {
try {
val pinTable = decryptData(hash)
pinTable = decryptData(hash)
withContext(Dispatchers.Main) {
binding.progressBar.hide()
colorTableView(pinTable)
fillTable(pinTable)
}
binding.exportFab.show()
} catch (e: EnDecryptionException) {
Log.d("CryptoManager", e.customStacktrace)
withContext(Dispatchers.Main) {
Expand Down Expand Up @@ -199,9 +218,9 @@ class PinViewerFragment : Fragment() {
private fun decryptData(hash: String): PinTable {
val file = File(myContext.filesDir, "p$hash")
val bytes = CryptoManager.decrypt(file)
val version = bytes.last()
siid = bytes.last()
@SuppressLint("SetTextI18n")
binding.siidView.text = "SIID: $version"
binding.siidView.text = "SIID: $siid"
return ObjectSerializer.deserialize(bytes.withoutLast()) as PinTable
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import androidx.fragment.app.FragmentActivity
import com.cyb3rko.pincredible.databinding.DialogProgressBinding
import com.google.android.material.dialog.MaterialAlertDialogBuilder

internal class ProgressDialog {
internal class ProgressDialog(private val indeterminate: Boolean) {
lateinit var dialogReference: AlertDialog
private set
lateinit var binding: DialogProgressBinding
Expand All @@ -36,6 +36,7 @@ internal class ProgressDialog {
) {
binding = DialogProgressBinding.inflate((context as FragmentActivity).layoutInflater)
binding.progressNote.text = initialNote
if (indeterminate) binding.progressBar.isIndeterminate = true

dialogReference = MaterialAlertDialogBuilder(context)
.setCancelable(false)
Expand Down
83 changes: 71 additions & 12 deletions app/src/main/kotlin/com/cyb3rko/pincredible/utils/BackupHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,18 @@ import java.text.SimpleDateFormat
import java.util.Date

internal object BackupHandler {
private const val SINGLE_BACKUP_FILE = ".pin"
private const val MULTI_BACKUP_FILE = ".pinc"
private const val INTEGRITY_CHECK = "INTGRTY"
private const val OVERHEAD_SIZE = INTEGRITY_CHECK.length + 1

@SuppressLint("SimpleDateFormat")
fun initiateSingleBackup(hash: String, launcher: ActivityResultLauncher<Intent>) {
val timestamp = SimpleDateFormat("yyyyMMdd-HHmmss").format(Date(System.currentTimeMillis()))
val fileName = "PIN-${hash.take(8)}-$timestamp$SINGLE_BACKUP_FILE"
showFileCreator(launcher, fileName)
}

@SuppressLint("SimpleDateFormat")
fun initiateBackup(context: Context, launcher: ActivityResultLauncher<Intent>) {
val fileList = context.fileList()
Expand All @@ -49,22 +57,67 @@ internal object BackupHandler {
}
}

fun runBackup(context: Context, uri: Uri) {
fun runBackup(
context: Context,
uri: Uri,
multiPin: Boolean,
singleBackup: SingleBackupStructure? = null
) {
PasswordDialog.show(
context,
R.string.dialog_backup_title
) { dialog, inputLayout, input ->
if (input.isNotEmpty() && input.length in 10..100) {
dialog.dismiss()
runExport(context, uri, CryptoManager.shaHash(input))
if (!multiPin) {
runSingleExport(context, uri, CryptoManager.shaHash(input), singleBackup!!)
} else {
runExport(context, uri, CryptoManager.shaHash(input))
}
} else {
inputLayout.error = context.getString(R.string.dialog_name_error_length, 10, 100)
}
}
}

private fun runSingleExport(
context: Context,
uri: Uri,
hash: String,
singleBackup: SingleBackupStructure
) {
val progressDialog = ProgressDialog(true).apply {
show(
context,
titleRes = R.string.dialog_export_title,
initialNote = context.getString(R.string.dialog_single_export_message)
)
}
val progressBar = progressDialog.binding.progressBar
val progressNote = progressDialog.binding.progressNote

try {
val bytes = ObjectSerializer.serialize(singleBackup)
val version = CryptoManager.SINGLE_BACKUP_CRYPTO_ITERATION.toByte()
CryptoManager.encrypt(
bytes.plus(version).plus(INTEGRITY_CHECK.encodeToByteArray()),
context.contentResolver.openOutputStream(uri),
hash.take(32)
)

progressBar.isIndeterminate = false
progressBar.progress = 100
progressNote.text = context.getString(R.string.dialog_single_export_finished)
progressDialog.dialogReference.setCancelable(true)
} catch (e: Exception) {
e.printStackTrace()
progressDialog.dialogReference.cancel()
ErrorDialog.show(context, e, R.string.dialog_export_error)
}
}

private fun runExport(context: Context, uri: Uri, hash: String) {
val progressDialog = ProgressDialog().apply {
val progressDialog = ProgressDialog(false).apply {
show(
context,
titleRes = R.string.dialog_export_title,
Expand All @@ -78,13 +131,13 @@ internal object BackupHandler {
val fileList = context.fileList()
if (fileList.size > 1) {
val progressStep = 50 / (fileList.size - 1)
val pins = mutableListOf<BackupPinTable>()
val pins = mutableListOf<MultiBackupPinTable>()
context.filesDir.listFiles()?.forEach {
if (it.name.startsWith("p") && it.name != CryptoManager.PINS_FILE) {
val bytes = CryptoManager.decrypt(it)
val version = bytes.last()
val pinTable = ObjectSerializer.deserialize(bytes.withoutLast()) as PinTable
pins.add(BackupPinTable(pinTable, version, it.name))
pins.add(MultiBackupPinTable(pinTable, version, it.name))
progressBar.progress = progressBar.progress + progressStep
progressNote.text = context.getString(
R.string.dialog_export_state_retrieving,
Expand All @@ -103,8 +156,8 @@ internal object BackupHandler {
CryptoManager.decrypt(nameFile)
) as Set<String>

val bytes = ObjectSerializer.serialize(BackupStructure(pins.toSet(), names))
val version = CryptoManager.BACKUP_CRYPTO_ITERATION.toByte()
val bytes = ObjectSerializer.serialize(MultiBackupStructure(pins.toSet(), names))
val version = CryptoManager.MULTI_BACKUP_CRYPTO_ITERATION.toByte()
CryptoManager.encrypt(
bytes.plus(version).plus(INTEGRITY_CHECK.encodeToByteArray()),
context.contentResolver.openOutputStream(uri),
Expand Down Expand Up @@ -153,7 +206,7 @@ internal object BackupHandler {
hash: String,
onFinished: () -> Unit
) {
val progressDialog = ProgressDialog().apply {
val progressDialog = ProgressDialog(false).apply {
show(
context,
titleRes = R.string.dialog_export_title,
Expand Down Expand Up @@ -183,7 +236,7 @@ internal object BackupHandler {
val version = bytes.nthLast(OVERHEAD_SIZE)
val backup = ObjectSerializer.deserialize(
bytes.withoutLastN(OVERHEAD_SIZE)
) as BackupStructure
) as MultiBackupStructure
progressBar.progress = 50
progressNote.text = context.getString(R.string.dialog_import_state_saving, 50)

Expand Down Expand Up @@ -260,14 +313,20 @@ internal object BackupHandler {
launcher.launch(intent)
}

class BackupPinTable(
class SingleBackupStructure(
val pinTable: PinTable,
val siid: Byte,
val name: String
) : Serializable

class MultiBackupPinTable(
val pinTable: PinTable,
val siid: Byte,
val fileName: String
) : Serializable

class BackupStructure(
val pins: Set<BackupPinTable>,
class MultiBackupStructure(
val pins: Set<MultiBackupPinTable>,
val names: Set<String>
) : Serializable
}
Loading

0 comments on commit c0bfca1

Please sign in to comment.