1package com.peachpasswords.vault23import android.content.Context4import dagger.hilt.android.qualifiers.ApplicationContext5import kotlinx.serialization.Serializable6import kotlinx.serialization.decodeFromString7import kotlinx.serialization.encodeToString8import kotlinx.serialization.json.Json9import javax.inject.Inject10import javax.inject.Singleton111213141516171819@Serializable20data class StoredVault(21 val version: Int = 1,22 val salt: String,23 val blob: String,24 val recoveryEscrow: RecoveryEscrow? = null,25 val rootKeyEscrow: RootKeyEscrow? = null,26)272829303132333435363738394041@Serializable42data class RootKeyEscrow(43 val salt: String,44 val wrappedRootKey: String,45 val nonce: String,46 val tag: String,47)4849505152535455565758@Serializable59data class RecoveryEscrow(60 val salt: String,61 val blob: String,62)636465@Serializable66data class Tombstone(val id: String, val deletedAt: Long)67686970717273747576777879@Serializable80data class VaultPayload(81 val entries: List<VaultEntry> = emptyList(),82 val duressConfigured: Boolean = false,83 val rootKeyB64: String = "",84 85868788 val recoveryWordHash: String? = null,89 90 val recoveryWordsSet: Boolean = false,91)929394object VaultCodec {95 private val json = Json { ignoreUnknownKeys = true; encodeDefaults = true }9697 98 99 100 101 102 fun encode(payload: VaultPayload): ByteArray =103 VaultCrypto.padToBucket(json.encodeToString(payload).toByteArray(Charsets.UTF_8))104 fun decode(bytes: ByteArray): VaultPayload =105 json.decodeFromString(String(bytes, Charsets.UTF_8).trimEnd('\u0000'))106 fun encodeSettings(s: VaultSettings): String = json.encodeToString(s)107 fun decodeSettings(s: String): VaultSettings? = runCatching { json.decodeFromString<VaultSettings>(s) }.getOrNull()108 fun encodeSlot(s: StoredVault): String = json.encodeToString(s)109 fun decodeSlot(s: String): StoredVault? = runCatching { json.decodeFromString<StoredVault>(s) }.getOrNull()110}111112113114115116117interface VaultStorage {118 fun loadSlotA(): StoredVault?119 fun hasVault(): Boolean120 fun writeSlotA(slot: StoredVault): Boolean121 fun writeSlotB(blobBase64: String): Boolean122 fun loadDecoySlot(): StoredVault?123 fun writeDecoySlot(slot: StoredVault): Boolean124 fun hasDecoySlot(): Boolean125 fun clearDecoySlot()126 fun version(): Long127 fun stampLocalMutation(): Long128 fun loadSettings(): VaultSettings129 fun writeSettings(settings: VaultSettings): Boolean130 fun clearVault()131}132133134135136137138139140@Singleton141class VaultStore @Inject constructor(142 @ApplicationContext context: Context,143) : VaultStorage {144 private val prefs = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)145146 override fun loadSlotA(): StoredVault? = prefs.getString(KEY_SLOT_A, null)?.let(VaultCodec::decodeSlot)147148 override fun hasVault(): Boolean = prefs.contains(KEY_SLOT_A)149150 override fun writeSlotA(slot: StoredVault): Boolean =151 prefs.edit().putString(KEY_SLOT_A, VaultCodec.encodeSlot(slot)).commit()152153 override fun writeSlotB(blobBase64: String): Boolean =154 prefs.edit().putString(KEY_SLOT_B, blobBase64).commit()155156 override fun loadDecoySlot(): StoredVault? = prefs.getString(KEY_SLOT_DURESS, null)?.let(VaultCodec::decodeSlot)157 override fun writeDecoySlot(slot: StoredVault): Boolean =158 prefs.edit().putString(KEY_SLOT_DURESS, VaultCodec.encodeSlot(slot)).commit()159 override fun hasDecoySlot(): Boolean = prefs.contains(KEY_SLOT_DURESS)160 override fun clearDecoySlot() { prefs.edit().remove(KEY_SLOT_DURESS).commit() }161162 override fun version(): Long = prefs.getString(KEY_VERSION, null)?.toLongOrNull() ?: 0L163164 override fun stampLocalMutation(): Long {165 val next = maxOf(System.currentTimeMillis(), version() + 1L)166 prefs.edit().putString(KEY_VERSION, next.toString()).commit()167 return next168 }169170 override fun loadSettings(): VaultSettings =171 prefs.getString(KEY_SETTINGS, null)?.let(VaultCodec::decodeSettings) ?: VaultSettings()172173 override fun writeSettings(settings: VaultSettings): Boolean =174 prefs.edit().putString(KEY_SETTINGS, VaultCodec.encodeSettings(settings)).commit()175176 override fun clearVault() {177 prefs.edit()178 .remove(KEY_SLOT_A).remove(KEY_SLOT_B).remove(KEY_VERSION).remove(KEY_SETTINGS)179 .commit()180 }181182 companion object {183 private const val PREFS = "peach_vault_store"184 private const val KEY_SLOT_A = "peach_vault_a"185 private const val KEY_SLOT_B = "peach_vault_b"186 private const val KEY_SLOT_DURESS = "peach_vault_duress"187 private const val KEY_VERSION = "peach_last_modified_at"188 private const val KEY_SETTINGS = "peach_settings"189 }190}