diff --git a/extension/js/common/core/crypto/pgp/pgp-armor.ts b/extension/js/common/core/crypto/pgp/pgp-armor.ts index d885f4cb73f..fd436899deb 100644 --- a/extension/js/common/core/crypto/pgp/pgp-armor.ts +++ b/extension/js/common/core/crypto/pgp/pgp-armor.ts @@ -69,17 +69,35 @@ export class PgpArmor { return !!new RegExp(`${PgpArmor.ARMOR_HEADER_DICT.encryptedMsg.begin}.*${PgpArmor.ARMOR_HEADER_DICT.encryptedMsg.end}`).exec(msg); } - public static clipIncomplete(text: string): string | undefined { - const match = text?.match(/(-----BEGIN PGP (MESSAGE|SIGNED MESSAGE|SIGNATURE|PUBLIC KEY BLOCK)-----[^]+)/gm); - return match?.length ? match[0] : undefined; - } - public static clip(text: string): string | undefined { - if (text?.includes(PgpArmor.ARMOR_HEADER_DICT.null.begin) && text.includes(String(PgpArmor.ARMOR_HEADER_DICT.null.end))) { - const match = text.match( - /(-----BEGIN PGP (MESSAGE|SIGNED MESSAGE|SIGNATURE|PUBLIC KEY BLOCK)-----[^]+-----END PGP (MESSAGE|SIGNATURE|PUBLIC KEY BLOCK)-----)/gm - ); - return match?.length ? match[0] : undefined; + // Prevent processing extremely large strings that could cause performance issues + const maxLength = 10 * 1024 * 1024; // 10MB limit + if (!text || text.length > maxLength) { + return undefined; + } + + if (text.includes(PgpArmor.ARMOR_HEADER_DICT.null.begin) && text.includes(String(PgpArmor.ARMOR_HEADER_DICT.null.end))) { + // Define the PGP armor headers we want to match + const pgpHeaders = [ + PgpArmor.ARMOR_HEADER_DICT.encryptedMsg, + PgpArmor.ARMOR_HEADER_DICT.signedMsg, + PgpArmor.ARMOR_HEADER_DICT.signature, + PgpArmor.ARMOR_HEADER_DICT.publicKey, + ]; + + // Build regex patterns from headers, escaping special regex characters + const patterns = pgpHeaders.map(header => { + const escapedBegin = header.begin.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + // header.end can be string or RegExp, handle both cases + const escapedEnd = typeof header.end === 'string' ? header.end.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') : header.end.source; // If it's already a RegExp, use its source + return `(${escapedBegin}[\\s\\S]*?${escapedEnd})`; + }); + + // Create regex with alternation, 'm' flag for multiline (no 'g' needed for single match) + const regex = new RegExp(patterns.join('|'), 'm'); + const match = text.match(regex); + + return match ? match[0] : undefined; } return undefined; } diff --git a/test/samples/openpgp/multialiaseduserexamplecom-0x357B908F62498DF8.asc b/test/samples/openpgp/multialiaseduserexamplecom-0x357B908F62498DF8.asc new file mode 100644 index 00000000000..aed0ce25d81 --- /dev/null +++ b/test/samples/openpgp/multialiaseduserexamplecom-0x357B908F62498DF8.asc @@ -0,0 +1,75 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- + +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 +T9hnbh8WlHmouxmPg+GyCDI= +=DSs3 +-----END PGP PRIVATE KEY BLOCK----- diff --git a/test/samples/openpgp/multialiaseduserexamplecom-0x357B908F62498DF8.key b/test/samples/openpgp/multialiaseduserexamplecom-0x357B908F62498DF8.key deleted file mode 100644 index dd6d90dcce1..00000000000 Binary files a/test/samples/openpgp/multialiaseduserexamplecom-0x357B908F62498DF8.key and /dev/null differ diff --git a/test/source/tests/setup.ts b/test/source/tests/setup.ts index 7160d7b8c51..ccac7b5b4d4 100644 --- a/test/source/tests/setup.ts +++ b/test/source/tests/setup.ts @@ -2660,7 +2660,7 @@ AN8G3r5Htj8olot+jm9mIa5XLXWzMNUZgg== const settingsPage = await BrowserRecipe.openSettingsLoginApprove(t, browser, acct); const key = { title: 'unarmored OpenPGP key', - filePath: 'test/samples/openpgp/multialiaseduserexamplecom-0x357B908F62498DF8.key', + filePath: 'test/samples/openpgp/multialiaseduserexamplecom-0x357B908F62498DF8.asc', armored: null, // eslint-disable-line no-null/no-null passphrase: '1basic passphrase to use', longid: null, // eslint-disable-line no-null/no-null diff --git a/test/source/tests/unit-node.ts b/test/source/tests/unit-node.ts index 8bb87b5f49f..cb246849a14 100644 --- a/test/source/tests/unit-node.ts +++ b/test/source/tests/unit-node.ts @@ -2755,10 +2755,44 @@ AAAAAAAAAAAAAAAAzzzzzzzzzzzzzzzzzzzzzzzzzzzz.....`) t.pass(); }); - test(`[unit][PgpArmor.clipIncomplete] correctly handles all the cases`, async t => { - expect(PgpArmor.clipIncomplete('')).to.be.an.undefined; - expect(PgpArmor.clipIncomplete('plain text')).to.be.an.undefined; - expect(PgpArmor.clipIncomplete('prefix -----BEGIN PGP MESSAGE-----\n\nexample')).to.equal('-----BEGIN PGP MESSAGE-----\n\nexample'); + test(`[unit][PgpArmor.clip] correctly handles all the cases including edge cases`, async t => { + // Test empty and plain text + expect(PgpArmor.clip('')).to.be.an.undefined; + expect(PgpArmor.clip('plain text')).to.be.an.undefined; + + // Test valid PGP messages + const validMessage = '-----BEGIN PGP MESSAGE-----\n\ntest content\n-----END PGP MESSAGE-----'; + expect(PgpArmor.clip(validMessage)).to.equal(validMessage); + + // Test with prefix and suffix + const messageWithNoise = 'prefix text ' + validMessage + ' suffix text'; + expect(PgpArmor.clip(messageWithNoise)).to.equal(validMessage); + + // Test multiple messages (should return first) + const multipleMessages = validMessage + '\n\n' + validMessage; + expect(PgpArmor.clip(multipleMessages)).to.equal(validMessage); + + // Test with different block types + const publicKeyBlock = '-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nkey data\n-----END PGP PUBLIC KEY BLOCK-----'; + expect(PgpArmor.clip(publicKeyBlock)).to.equal(publicKeyBlock); + + // Test with signed message + const signedMessage = '-----BEGIN PGP SIGNED MESSAGE-----\n\nmessage\n-----BEGIN PGP SIGNATURE-----\nsig\n-----END PGP SIGNATURE-----'; + expect(PgpArmor.clip(signedMessage)).to.equal(signedMessage); + + // Test with mismatched begin/end (should not match) + const mismatchedMessage = '-----BEGIN PGP MESSAGE-----\n\ntest\n-----END PGP SIGNATURE-----'; + expect(PgpArmor.clip(mismatchedMessage)).to.be.an.undefined; + + // Test with very large input (over 10MB limit) + const largeInput = 'a'.repeat(11 * 1024 * 1024); + expect(PgpArmor.clip(largeInput)).to.be.an.undefined; + + // Test with pathological input that would have caused stack overflow + const pathologicalBase = '-----BEGIN PGP MESSAGE-----\n' + 'a'.repeat(1000); + const pathologicalInput = pathologicalBase + '\n-----END PGP MESSAGE-----'; + expect(PgpArmor.clip(pathologicalInput)).to.equal(pathologicalInput); + t.pass(); });