Merge a3a934d06b7ff4540b3afb3371a70c51e043e65c into b374d37d4b8270d433b4f5c0e1a79a68807576cd

This commit is contained in:
Daniel Huigens 2025-11-14 12:31:46 +01:00 committed by GitHub
commit 4cf1bb835f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 40 additions and 10 deletions

View File

@ -37,8 +37,8 @@ export class CleartextMessage {
* @param {Signature} signature - The detached signature or an empty signature for unsigned messages * @param {Signature} signature - The detached signature or an empty signature for unsigned messages
*/ */
constructor(text, signature) { constructor(text, signature) {
// remove trailing whitespace and normalize EOL to canonical form <CR><LF> // remove trailing whitespace from each line and normalize line endings to LF
this.text = util.removeTrailingSpaces(text).replace(/\r?\n/g, '\r\n'); this.text = util.removeTrailingSpaces(text);
if (signature && !(signature instanceof Signature)) { if (signature && !(signature instanceof Signature)) {
throw new Error('Invalid signature input'); throw new Error('Invalid signature input');
} }
@ -74,7 +74,8 @@ export class CleartextMessage {
*/ */
async sign(signingKeys, recipientKeys = [], signature = null, signingKeyIDs = [], date = new Date(), signingUserIDs = [], recipientUserIDs = [], notations = [], config = defaultConfig) { async sign(signingKeys, recipientKeys = [], signature = null, signingKeyIDs = [], date = new Date(), signingUserIDs = [], recipientUserIDs = [], notations = [], config = defaultConfig) {
const literalDataPacket = new LiteralDataPacket(); const literalDataPacket = new LiteralDataPacket();
literalDataPacket.setText(this.text); // normalize EOL to canonical form <CR><LF>
literalDataPacket.setText(this.text.replace(/\n/g, '\r\n'));
const newSignature = new Signature(await createSignaturePackets(literalDataPacket, signingKeys, recipientKeys, signature, signingKeyIDs, date, signingUserIDs, recipientUserIDs, notations, true, config)); const newSignature = new Signature(await createSignaturePackets(literalDataPacket, signingKeys, recipientKeys, signature, signingKeyIDs, date, signingUserIDs, recipientUserIDs, notations, true, config));
return new CleartextMessage(this.text, newSignature); return new CleartextMessage(this.text, newSignature);
} }
@ -94,8 +95,8 @@ export class CleartextMessage {
verify(keys, date = new Date(), config = defaultConfig) { verify(keys, date = new Date(), config = defaultConfig) {
const signatureList = this.signature.packets.filterByTag(enums.packet.signature); // drop UnparsablePackets const signatureList = this.signature.packets.filterByTag(enums.packet.signature); // drop UnparsablePackets
const literalDataPacket = new LiteralDataPacket(); const literalDataPacket = new LiteralDataPacket();
// we assume that cleartext signature is generated based on UTF8 cleartext // cleartext signatures should be generated over UTF8 cleartext with canonical newlines
literalDataPacket.setText(this.text); literalDataPacket.setText(this.text.replace(/\n/g, '\r\n'));
return createVerificationObjects(signatureList, [literalDataPacket], keys, date, true, config); return createVerificationObjects(signatureList, [literalDataPacket], keys, date, true, config);
} }
@ -104,8 +105,7 @@ export class CleartextMessage {
* @returns {String} Cleartext of message. * @returns {String} Cleartext of message.
*/ */
getText() { getText() {
// normalize end of line to \n return this.text;
return this.text.replace(/\r\n/g, '\n');
} }
/** /**

View File

@ -273,7 +273,7 @@ export function unarmor(input) {
// Reverse dash-escaping for msg // Reverse dash-escaping for msg
text.push(line.replace(/^- /, '')); text.push(line.replace(/^- /, ''));
} else { } else {
text = text.join('\r\n'); text = text.join('\n');
textDone = true; textDone = true;
verifyHeaders(lastHeaders); verifyHeaders(lastHeaders);
lastHeaders = []; lastHeaders = [];

View File

@ -252,7 +252,7 @@ export default () => describe('ASCII armor', function() {
it('Do not filter blank lines after header', async function () { it('Do not filter blank lines after header', async function () {
let msg = getArmor(['Hash: SHA1', '']); let msg = getArmor(['Hash: SHA1', '']);
msg = await openpgp.readCleartextMessage({ cleartextMessage: msg }); msg = await openpgp.readCleartextMessage({ cleartextMessage: msg });
expect(msg.text).to.equal('\r\nsign this'); expect(msg.getText()).to.equal('\nsign this');
}); });
it('Selectively output CRC checksum', async function () { it('Selectively output CRC checksum', async function () {
@ -353,9 +353,39 @@ NJCB6+LWtabSoVIjNVgKwyKqyTLaESNwC2ogZwkdE8qPGiDFEHo4Gg9zuRof
).to.equal( ).to.equal(
pubKey pubKey
.replace('\n-', '-') .replace('\n-', '-')
.replace(/\n\r/g, '\n')
); );
}); });
it('Armored messages use LF newlines', async function () {
const usesLF = armoredData => {
return !armoredData.includes('\r');
};
const { privateKey: v4Key } = await openpgp.generateKey({ userIDs: { email: 'v4@armor.test' }, format: 'object' });
expect(usesLF(v4Key.armor())).to.be.true;
const { privateKey: v6Key } = await openpgp.generateKey({ userIDs: { email: 'v6@armor.test' }, config: { v6Keys: true, aeadProtect: true }, format: 'object' });
expect(usesLF(v6Key.armor())).to.be.true;
const messageWithSEIPDv1 = await openpgp.encrypt({ message: await openpgp.createMessage({ text: 'test' }), encryptionKeys: v4Key });
expect(usesLF(messageWithSEIPDv1)).to.be.true;
const messageWithSEIPDv2 = await openpgp.encrypt({ message: await openpgp.createMessage({ text: 'test' }), encryptionKeys: v6Key });
expect(usesLF(messageWithSEIPDv2)).to.be.true;
const signatureV4V6 = await openpgp.sign({ message: await openpgp.createMessage({ text: 'test' }), signingKeys: [v4Key, v6Key] });
expect(usesLF(signatureV4V6)).to.be.true;
const signatureV6 = await openpgp.sign({ message: await openpgp.createMessage({ text: 'test' }), signingKeys: v6Key });
expect(usesLF(signatureV6)).to.be.true;
const detachedSignatureV4V6 = await openpgp.sign({ message: await openpgp.createMessage({ text: 'test' }), signingKeys: [v4Key, v6Key], detached: true });
expect(usesLF(detachedSignatureV4V6)).to.be.true;
const detachedSignatureV6 = await openpgp.sign({ message: await openpgp.createMessage({ text: 'test' }), signingKeys: v6Key, detached: true });
expect(usesLF(detachedSignatureV6)).to.be.true;
const cleartextSignatureV4V6 = await openpgp.sign({ message: await openpgp.createCleartextMessage({ text: 'test' }), signingKeys: [v4Key, v6Key] });
expect(usesLF(cleartextSignatureV4V6)).to.be.true;
const cleartextSignatureV6 = await openpgp.sign({ message: await openpgp.createCleartextMessage({ text: 'test' }), signingKeys: v6Key });
expect(usesLF(cleartextSignatureV6)).to.be.true;
});
}); });