Add back armor checksum to detached signatures for GPG compatibility

GPG v2 fails to parse detached signatures without the checksum
This commit is contained in:
larabr 2024-03-22 16:07:49 +01:00
parent cb97c8fcb9
commit b41298a3f6

View File

@ -107,6 +107,78 @@ function addheader(customComment, config) {
return result;
}
/**
* Calculates a checksum over the given data and returns it base64 encoded
* @param {String | ReadableStream<String>} data - Data to create a CRC-24 checksum for
* @returns {String | ReadableStream<String>} Base64 encoded checksum.
* @private
*/
function getCheckSum(data) {
const crc = createcrc24(data);
return base64.encode(crc);
}
// https://create.stephan-brumme.com/crc32/#slicing-by-8-overview
const crc_table = [
new Array(0xFF),
new Array(0xFF),
new Array(0xFF),
new Array(0xFF)
];
for (let i = 0; i <= 0xFF; i++) {
let crc = i << 16;
for (let j = 0; j < 8; j++) {
crc = (crc << 1) ^ ((crc & 0x800000) !== 0 ? 0x864CFB : 0);
}
crc_table[0][i] =
((crc & 0xFF0000) >> 16) |
(crc & 0x00FF00) |
((crc & 0x0000FF) << 16);
}
for (let i = 0; i <= 0xFF; i++) {
crc_table[1][i] = (crc_table[0][i] >> 8) ^ crc_table[0][crc_table[0][i] & 0xFF];
}
for (let i = 0; i <= 0xFF; i++) {
crc_table[2][i] = (crc_table[1][i] >> 8) ^ crc_table[0][crc_table[1][i] & 0xFF];
}
for (let i = 0; i <= 0xFF; i++) {
crc_table[3][i] = (crc_table[2][i] >> 8) ^ crc_table[0][crc_table[2][i] & 0xFF];
}
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView#Endianness
const isLittleEndian = (function() {
const buffer = new ArrayBuffer(2);
new DataView(buffer).setInt16(0, 0xFF, true /* littleEndian */);
// Int16Array uses the platform's endianness.
return new Int16Array(buffer)[0] === 0xFF;
}());
/**
* Internal function to calculate a CRC-24 checksum over a given string (data)
* @param {String | ReadableStream<String>} input - Data to create a CRC-24 checksum for
* @returns {Uint8Array | ReadableStream<Uint8Array>} The CRC-24 checksum.
* @private
*/
function createcrc24(input) {
let crc = 0xCE04B7;
return stream.transform(input, value => {
const len32 = isLittleEndian ? Math.floor(value.length / 4) : 0;
const arr32 = new Uint32Array(value.buffer, value.byteOffset, len32);
for (let i = 0; i < len32; i++) {
crc ^= arr32[i];
crc =
crc_table[0][(crc >> 24) & 0xFF] ^
crc_table[1][(crc >> 16) & 0xFF] ^
crc_table[2][(crc >> 8) & 0xFF] ^
crc_table[3][(crc >> 0) & 0xFF];
}
for (let i = len32 * 4; i < value.length; i++) {
crc = (crc >> 8) ^ crc_table[0][(crc & 0xFF) ^ value[i]];
}
}, () => new Uint8Array([crc, crc >> 8, crc >> 16]));
}
/**
* Verify armored headers. crypto-refresh-06, section 6.2:
@ -314,12 +386,18 @@ export function armor(messageType, body, partIndex, partTotal, customComment, co
result.push(base64.encode(body));
result.push('-----END PGP PRIVATE KEY BLOCK-----\n');
break;
case enums.armor.signature:
case enums.armor.signature: {
const bodyClone = stream.passiveClone(body);
result.push('-----BEGIN PGP SIGNATURE-----\n');
result.push(addheader(customComment, config));
result.push(base64.encode(body));
// GPG v2 fails to parse signatures without checksums
result.push('=', getCheckSum(bodyClone));
result.push('-----END PGP SIGNATURE-----\n');
break;
}
default:
throw new Error('Unknown armor type');
}
return util.concat(result);