From e8b0085689d9f3bca036d405c12229a9e5fe7904 Mon Sep 17 00:00:00 2001 From: LC mac Date: Wed, 4 Feb 2026 13:22:19 +0800 Subject: [PATCH] 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. --- src/lib/seedpgp.ts | 39 ++++++++++++++++++++++++--------------- src/lib/types.ts | 1 + 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/lib/seedpgp.ts b/src/lib/seedpgp.ts index d2034ce..4c04995 100644 --- a/src/lib/seedpgp.ts +++ b/src/lib/seedpgp.ts @@ -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.`); diff --git a/src/lib/types.ts b/src/lib/types.ts index dd5ad60..d806aaa 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -11,6 +11,7 @@ export type ParsedSeedPgpFrame = { kind: "single"; crc16: string; b45: string; + rawPayload?: boolean; }; // Krux KEF types