PeachScriptImport.kt

2.7 KB · raw

1package com.peachpasswords.ui.vault23import com.peachpasswords.vault.PeachPayloadCodec4import com.peachpasswords.vault.PeachScript5import com.peachpasswords.vault.VaultEntry6import com.peachpasswords.vault.VaultSettings78/**9 * PeachScript import (MF-2) — parses the browser's PCHS binary payload back into10 * full vault entries. The PUA text is decoded to bytes ([PeachScript.decode]),11 * then [PeachPayloadCodec.parsePayload] decrypts (Argon2id + AES-256-GCM),12 * verifies the BLAKE3 checksum, zstd-decompresses each section, and unpacks the13 * entries — every field the browser exported (tags, card fields, canary, breach,14 * timestamps, passkey) is preserved.15 */16object PeachScriptImport {1718    /** Outcome of decrypting + parsing an encrypted export. */19    sealed interface DecryptResult {20        data class Success(21            val entries: List<VaultEntry>,22            val settings: VaultSettings = VaultSettings(),23            val licenseKey: String? = null,24            val licenseActivatedAt: Long? = null,25        ) : DecryptResult26        data object WrongPassword : DecryptResult27        data object Corrupted : DecryptResult28    }2930    /** Cheap sniff: does this look like PeachScript (PUA-encoded) text? */31    fun looksLikePeachScript(text: String): Boolean = PeachScript.isPeachScript(text)3233    /** PeachScript is always encrypted (backup-password protected). */34    fun isEncrypted(text: String): Boolean = looksLikePeachScript(text)3536    /**37     * Decrypt a PeachScript export with the user's backup password and parse the38     * inner entries. Maps [PeachPayloadCodec] errors to the UI's result states:39     * a GCM tag mismatch (INVALID_PASSPHRASE) surfaces as "Incorrect backup40     * password"; structural failures surface as corrupted.41     */42    fun decryptAndParse(text: String, backupPassword: String): DecryptResult {43        val normalised = PeachScript.normalise(text)44        if (!PeachScript.isPeachScript(normalised)) return DecryptResult.Corrupted45        val bytes = try {46            PeachScript.decode(normalised)47        } catch (_: Exception) {48            return DecryptResult.Corrupted49        }50        return try {51            val payload = PeachPayloadCodec.parsePayloadData(bytes, backupPassword)52            DecryptResult.Success(53                entries = payload.entries,54                settings = payload.settings,55                licenseKey = payload.licenseKey,56                licenseActivatedAt = payload.licenseActivatedAt,57            )58        } catch (e: PeachPayloadCodec.PeachScriptException) {59            if (e.message == "INVALID_PASSPHRASE") DecryptResult.WrongPassword else DecryptResult.Corrupted60        } catch (_: Exception) {61            DecryptResult.Corrupted62        }63    }64}