package com.peachpasswords.ui.vault import com.peachpasswords.vault.PeachPayloadCodec import com.peachpasswords.vault.PeachScript import com.peachpasswords.vault.VaultEntry /** * PeachScript import (MF-2) — parses the browser's PCHS binary payload back into * full vault entries. The PUA text is decoded to bytes ([PeachScript.decode]), * then [PeachPayloadCodec.parsePayload] decrypts (Argon2id + AES-256-GCM), * verifies the BLAKE3 checksum, zstd-decompresses each section, and unpacks the * entries — every field the browser exported (tags, card fields, canary, breach, * timestamps, passkey) is preserved. */ object PeachScriptImport { /** Outcome of decrypting + parsing an encrypted export. */ sealed interface DecryptResult { data class Success(val entries: List) : DecryptResult data object WrongPassword : DecryptResult data object Corrupted : DecryptResult } /** Cheap sniff: does this look like PeachScript (PUA-encoded) text? */ fun looksLikePeachScript(text: String): Boolean = PeachScript.isPeachScript(text) /** PeachScript is always encrypted (backup-password protected). */ fun isEncrypted(text: String): Boolean = looksLikePeachScript(text) /** * Decrypt a PeachScript export with the user's backup password and parse the * inner entries. Maps [PeachPayloadCodec] errors to the UI's result states: * a GCM tag mismatch (INVALID_PASSPHRASE) surfaces as "Incorrect backup * password"; structural failures surface as corrupted. */ fun decryptAndParse(text: String, backupPassword: String): DecryptResult { val normalised = PeachScript.normalise(text) if (!PeachScript.isPeachScript(normalised)) return DecryptResult.Corrupted val bytes = try { PeachScript.decode(normalised) } catch (_: Exception) { return DecryptResult.Corrupted } return try { DecryptResult.Success(PeachPayloadCodec.parsePayload(bytes, backupPassword)) } catch (e: PeachPayloadCodec.PeachScriptException) { if (e.message == "INVALID_PASSPHRASE") DecryptResult.WrongPassword else DecryptResult.Corrupted } catch (_: Exception) { DecryptResult.Corrupted } } }