PeachScriptImport.kt

2.2 KB · raw

1package com.peachpasswords.ui.vault23import com.peachpasswords.vault.PeachPayloadCodec4import com.peachpasswords.vault.PeachScript5import com.peachpasswords.vault.VaultEntry67/**8 * PeachScript import (MF-2) — parses the browser's PCHS binary payload back into9 * full vault entries. The PUA text is decoded to bytes ([PeachScript.decode]),10 * then [PeachPayloadCodec.parsePayload] decrypts (Argon2id + AES-256-GCM),11 * verifies the BLAKE3 checksum, zstd-decompresses each section, and unpacks the12 * entries — every field the browser exported (tags, card fields, canary, breach,13 * timestamps, passkey) is preserved.14 */15object PeachScriptImport {1617    /** Outcome of decrypting + parsing an encrypted export. */18    sealed interface DecryptResult {19        data class Success(val entries: List<VaultEntry>) : DecryptResult20        data object WrongPassword : DecryptResult21        data object Corrupted : DecryptResult22    }2324    /** Cheap sniff: does this look like PeachScript (PUA-encoded) text? */25    fun looksLikePeachScript(text: String): Boolean = PeachScript.isPeachScript(text)2627    /** PeachScript is always encrypted (backup-password protected). */28    fun isEncrypted(text: String): Boolean = looksLikePeachScript(text)2930    /**31     * Decrypt a PeachScript export with the user's backup password and parse the32     * inner entries. Maps [PeachPayloadCodec] errors to the UI's result states:33     * a GCM tag mismatch (INVALID_PASSPHRASE) surfaces as "Incorrect backup34     * password"; structural failures surface as corrupted.35     */36    fun decryptAndParse(text: String, backupPassword: String): DecryptResult {37        val normalised = PeachScript.normalise(text)38        if (!PeachScript.isPeachScript(normalised)) return DecryptResult.Corrupted39        val bytes = try {40            PeachScript.decode(normalised)41        } catch (_: Exception) {42            return DecryptResult.Corrupted43        }44        return try {45            DecryptResult.Success(PeachPayloadCodec.parsePayload(bytes, backupPassword))46        } catch (e: PeachPayloadCodec.PeachScriptException) {47            if (e.message == "INVALID_PASSPHRASE") DecryptResult.WrongPassword else DecryptResult.Corrupted48        } catch (_: Exception) {49            DecryptResult.Corrupted50        }51    }52}