fix(parser): handle raw SeedPGP payloads without prefix

Modifies the frame parsing logic to accommodate Base45-encoded SeedPGP payloads that are missing the 'SEEDPGP1:' prefix and CRC. This scenario occurs with certain QR code generators, such as some modes on Krux devices.

- `frameParse` now detects when the prefix is missing and treats the entire input as a raw Base45 payload.
- A `rawPayload` flag is added to the `ParsedSeedPgpFrame` type to signal this case.
- `frameDecodeToPgpBytes` now bypasses the CRC check when this flag is true, allowing the decryption to proceed.
- This resolves a bug where valid encrypted payloads were being rejected due to a missing frame structure.
This commit is contained in:
LC mac
2026-02-04 13:22:19 +08:00
parent 26fb4ca92e
commit e8b0085689
2 changed files with 25 additions and 15 deletions

View File

@@ -53,28 +53,37 @@ export function frameEncode(pgpBinary: Uint8Array): string {
export function frameParse(text: string): ParsedSeedPgpFrame {
const s = text.trim().replace(/^["']|["']$/g, "").replace(/[\n\r\t]/g, "");
if (!s.startsWith("SEEDPGP1:")) throw new Error("Missing SEEDPGP1: prefix");
if (s.startsWith("SEEDPGP1:")) {
const parts = s.split(":");
if (parts.length < 4) {
throw new Error("Invalid frame format (need at least 4 colon-separated parts)");
}
const prefix = parts[0];
const frame = parts[1];
const crc16 = parts[2].toUpperCase();
const b45 = parts.slice(3).join(":");
const parts = s.split(":");
if (parts.length < 4) {
throw new Error("Invalid frame format (need at least 4 colon-separated parts)");
if (prefix !== "SEEDPGP1") throw new Error("Invalid prefix");
if (frame !== "0") throw new Error("Multipart frames not supported in this prototype");
if (!/^[0-9A-F]{4}$/.test(crc16)) throw new Error("Invalid CRC16 format (must be 4 hex chars)");
return { kind: "single", crc16, b45 };
} else {
// It's not a full frame. Assume the ENTIRE string is the base45 payload.
// We will have to skip the CRC check.
return { kind: "single", crc16: "0000", b45: s, rawPayload: true };
}
const prefix = parts[0];
const frame = parts[1];
const crc16 = parts[2].toUpperCase();
const b45 = parts.slice(3).join(":");
if (prefix !== "SEEDPGP1") throw new Error("Invalid prefix");
if (frame !== "0") throw new Error("Multipart frames not supported in this prototype");
if (!/^[0-9A-F]{4}$/.test(crc16)) throw new Error("Invalid CRC16 format (must be 4 hex chars)");
return { kind: "single", crc16, b45 };
}
export function frameDecodeToPgpBytes(frameText: string): Uint8Array {
const f = frameParse(frameText);
const pgp = base45Decode(f.b45);
// If it's a raw payload, we cannot and do not verify the CRC.
if (f.rawPayload) {
return pgp;
}
const crc = crc16CcittFalse(pgp);
if (crc !== f.crc16) {
throw new Error(`CRC16 mismatch! Expected: ${f.crc16}, Got: ${crc}. QR scan may be corrupted.`);

View File

@@ -11,6 +11,7 @@ export type ParsedSeedPgpFrame = {
kind: "single";
crc16: string;
b45: string;
rawPayload?: boolean;
};
// Krux KEF types