Skip to content

Commit 6b747be

Browse files
author
Ioan Moldovan
authored
fix: invalid checksum mismatch error in signed messages with public key attached (#5950)
1 parent 4c49193 commit 6b747be

File tree

4 files changed

+207
-35
lines changed

4 files changed

+207
-35
lines changed

extension/chrome/elements/pgp_block_modules/pgp-block-render-module.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export class PgpBlockViewRenderModule {
4949
public renderContent = (htmlContent: string, isErr: boolean, isChecksumInvalid = false) => {
5050
let contentWithLink = linkifyHtml(htmlContent);
5151
if (isChecksumInvalid) {
52-
contentWithLink = `<div class="pgp-invalid-checksum">${Lang.pgpBlock.invalidCheckSum}</div>${contentWithLink}}`;
52+
contentWithLink = `<div class="pgp-invalid-checksum">${Lang.pgpBlock.invalidCheckSum}</div>${contentWithLink}`;
5353
}
5454
// Temporary workaround for an issue where 'cryptup_reply' divs are not being hidden when replying to all
5555
// messages from the FES. The root cause is that FES currently returns the body of

extension/js/common/core/crypto/key.ts

Lines changed: 57 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -489,48 +489,71 @@ export class KeyUtil {
489489
return keys.map(k => ({ id: k.id, emails: k.emails, armored: KeyUtil.armor(k), family: k.family }));
490490
}
491491

492-
public static validateChecksum = (armoredText: string): boolean => {
493-
const lines = armoredText.split('\n').map(l => l.trim());
494-
495-
// Filter out known non-data lines
496-
const dataCandidates = lines.filter(line => line.length > 0 && !line.startsWith('-----') && !line.startsWith('Version:') && !line.startsWith('Comment:'));
492+
public static validateChecksum(armoredText: string): boolean {
493+
// Regex to capture any PGP armor block, e.g. SIGNATURE, MESSAGE, etc.
494+
// It captures: the block type in group (1), and the content in group (2)
495+
const pgpBlockRegex = /-----BEGIN PGP ([A-Z ]+)-----([\s\S]*?)-----END PGP \1-----/g;
496+
497+
let match: RegExpExecArray | null;
498+
let validFound = false;
499+
500+
// Iterate over all PGP blocks in the text
501+
while ((match = pgpBlockRegex.exec(armoredText))) {
502+
const blockContent = match[2] || ''; // the captured block text
503+
const lines = blockContent.split('\n').map(l => l.trim());
504+
505+
// Filter out known non-base64 lines
506+
const dataCandidates = lines.filter(
507+
line => line.length > 0 && !line.startsWith('Version:') && !line.startsWith('Comment:') && !line.startsWith('Hash:') && !line.startsWith('-----')
508+
);
509+
510+
// Find the checksum line (starts with '=')
511+
const checksumIndex = dataCandidates.findIndex(line => line.startsWith('='));
512+
if (checksumIndex === -1) {
513+
continue; // No checksum line found in this block, skip
514+
}
497515

498-
// Find checksum line
499-
const checksumIndex = dataCandidates.findIndex(line => line.startsWith('='));
500-
if (checksumIndex === -1) return false;
516+
// Get the base64 after the '='
517+
const checksumLine = dataCandidates[checksumIndex].slice(1);
518+
let providedBytes: string;
519+
try {
520+
providedBytes = atob(checksumLine);
521+
} catch {
522+
continue; // Not valid base64, skip
523+
}
501524

502-
const checksumLine = dataCandidates[checksumIndex].slice(1);
503-
const dataLines = dataCandidates.slice(0, checksumIndex);
525+
// Check that decoded checksum is 3 bytes for CRC24
526+
if (providedBytes.length !== 3) {
527+
continue;
528+
}
529+
const providedCRC = (providedBytes.charCodeAt(0) << 16) | (providedBytes.charCodeAt(1) << 8) | providedBytes.charCodeAt(2);
504530

505-
// Decode checksum
506-
let providedBytes: string;
507-
try {
508-
providedBytes = atob(checksumLine);
509-
} catch {
510-
return false;
511-
}
512-
if (providedBytes.length !== 3) return false;
531+
// Decode all lines before the checksum line
532+
const dataLines = dataCandidates.slice(0, checksumIndex);
533+
const decodedChunks: string[] = [];
534+
for (const line of dataLines) {
535+
try {
536+
decodedChunks.push(atob(line));
537+
} catch {
538+
// skip lines that aren't valid base64
539+
}
540+
}
513541

514-
const providedCRC = (providedBytes.charCodeAt(0) << 16) | (providedBytes.charCodeAt(1) << 8) | providedBytes.charCodeAt(2);
542+
if (!decodedChunks.length) {
543+
continue;
544+
}
515545

516-
// Attempt to decode all data lines (some may not be base64)
517-
const decodedChunks: string[] = [];
518-
for (const line of dataLines) {
519-
try {
520-
decodedChunks.push(atob(line));
521-
} catch {
522-
// Not a valid base64 line, skip it
546+
// Join all decoded base64 data and calculate its CRC
547+
const rawData = decodedChunks.join('');
548+
// eslint-disable-next-line @typescript-eslint/no-misused-spread
549+
const dataBytes = new Uint8Array([...rawData].map(c => c.charCodeAt(0)));
550+
if (KeyUtil.crc24(dataBytes) === providedCRC) {
551+
validFound = true;
523552
}
524553
}
525554

526-
if (decodedChunks.length === 0) return false;
527-
528-
const rawData = decodedChunks.join('');
529-
// eslint-disable-next-line @typescript-eslint/no-misused-spread
530-
const dataBytes = new Uint8Array([...rawData].map(c => c.charCodeAt(0)));
531-
532-
return KeyUtil.crc24(dataBytes) === providedCRC;
533-
};
555+
return validFound;
556+
}
534557

535558
private static crc24 = (dataBytes: Uint8Array): number => {
536559
let crc = 0xb704ce;

0 commit comments

Comments
 (0)