diff --git a/src/encoding/armor.js b/src/encoding/armor.js index 53fdd49a..7caead0f 100644 --- a/src/encoding/armor.js +++ b/src/encoding/armor.js @@ -192,6 +192,26 @@ function verifyHeaders(headers) { } } +/** + * Splits a message into two parts, the body and the checksum. This is an internal function + * @param {String} text OpenPGP armored message part + * @returns {Object} An object with attribute "body" containing the body + * and an attribute "checksum" containing the checksum. + */ +function splitChecksum(text) { + let body = text; + let checksum = ""; + + const lastEquals = text.lastIndexOf("="); + + if (lastEquals >= 0 && lastEquals !== text.length - 1) { // '=' as the last char means no checksum + body = text.slice(0, lastEquals); + checksum = text.slice(lastEquals + 1).substr(0, 4); + } + + return { body: body, checksum: checksum }; +} + /** * DeArmor an OpenPGP armored message; verify the checksum and return * the encoded bytes @@ -204,7 +224,7 @@ function verifyHeaders(headers) { function dearmor(input) { return new Promise(async (resolve, reject) => { try { - const reSplit = /^-----[^-]+-----$/; + const reSplit = /^-----[^-]+-----$/m; const reEmptyLine = /^[ \f\r\t\u00a0\u2000-\u200a\u202f\u205f\u3000]*$/; let type; @@ -213,21 +233,17 @@ function dearmor(input) { let headersDone; let text = []; let textDone; - let resolved = false; let checksum; let data = base64.decode(stream.transformPair(input, async (readable, writable) => { const reader = stream.getReader(readable); - const writer = stream.getWriter(writable); - while (true) { - if (resolved) await writer.ready; - try { - const lineUntrimmed = await reader.readLine(); - if (lineUntrimmed === undefined) { + try { + while (true) { + let line = await reader.readLine(); + if (line === undefined) { throw new Error('Misformed armored text'); } // remove trailing whitespace at end of lines - // remove leading whitespace for compat with older versions of OpenPGP.js - const line = lineUntrimmed.trim(); + line = line.replace(/[\t\r\n ]+$/, ''); if (!type) { if (reSplit.test(line)) { type = getType(line); @@ -243,13 +259,13 @@ function dearmor(input) { headersDone = true; if (textDone || type !== 2) { resolve({ text, data, headers, type }); - resolved = true; + break; } } } else if (!textDone && type === 2) { if (!reSplit.test(line)) { // Reverse dash-escaping for msg - text.push(util.removeTrailingSpaces(lineUntrimmed.replace(/^- /, '').replace(/[\r\n]+$/, ''))); + text.push(line.replace(/^- /, '')); } else { text = text.join('\r\n'); textDone = true; @@ -257,26 +273,40 @@ function dearmor(input) { lastHeaders = []; headersDone = false; } - } else { - if (!reSplit.test(line)) { - if (line[0] !== '=') { - await writer.write(line); - } else { - checksum = line.substr(1); - } - } else { - await writer.close(); - break; - } } - } catch(e) { - if (resolved) { - await writer.abort(e); - } else { - reject(e); - } - break; } + } catch(e) { + reject(e); + return; + } + const writer = stream.getWriter(writable); + try { + while (true) { + await writer.ready; + const { done, value } = await reader.read(); + if (done) { + throw new Error('Misformed armored text'); + } + const line = value + ''; + if (line.indexOf('=') === -1 && line.indexOf('-') === -1) { + await writer.write(line); + } else { + let remainder = line + await reader.readToEnd(); + remainder = remainder.replace(/[\t\r ]+$/mg, ''); + const parts = remainder.split(reSplit); + if (parts.length === 1) { + throw new Error('Misformed armored text'); + } + const split = splitChecksum(parts[0].slice(0, -1)); + checksum = split.checksum; + await writer.write(split.body); + break; + } + } + await writer.ready; + await writer.close(); + } catch(e) { + await writer.abort(e); } })); data = stream.transformPair(data, async (readable, writable) => { diff --git a/src/packet/compressed.js b/src/packet/compressed.js index a9e4cd00..cd6bb44f 100644 --- a/src/packet/compressed.js +++ b/src/packet/compressed.js @@ -142,8 +142,10 @@ function pako_zlib(constructor, options = {}) { return function(data) { const obj = new constructor(options); return stream.transform(data, value => { - obj.push(value, pako.Z_SYNC_FLUSH); - return obj.result; + if (value.length) { + obj.push(value, pako.Z_SYNC_FLUSH); + return obj.result; + } }); }; } diff --git a/src/packet/packet.js b/src/packet/packet.js index 874431c2..b5ec1081 100644 --- a/src/packet/packet.js +++ b/src/packet/packet.js @@ -256,14 +256,13 @@ export default { packet = await reader.readBytes(packet_length); await callback({ tag, packet }); } - const { done, value } = await reader.read(); - if (!done) reader.unshift(value); + const nextPacket = await reader.peekBytes(2); if (writer) { await writer.ready; await writer.close(); } if (streaming) await callbackReturned; - return done || !value || !value.length; + return !nextPacket || !nextPacket.length; } catch(e) { if (writer) { await writer.abort(e); diff --git a/test/general/armor.js b/test/general/armor.js index f2b6c5a6..2d924d1b 100644 --- a/test/general/armor.js +++ b/test/general/armor.js @@ -304,7 +304,7 @@ describe("ASCII armor", function() { it('Accept header with trailing whitespace', async function () { const privKey = - ['-----BEGIN PGP PRIVATE KEY BLOCK-----\t \r', + ['-----BEGIN PGP PRIVATE KEY BLOCK-----', 'Version: OpenPGP.js v0.3.0', 'Comment: https://openpgpjs.org', '', @@ -321,7 +321,8 @@ describe("ASCII armor", function() { 'ABMFAlLm1+4JEBD8MASZrpALAhsMAAC3IgD8DnLGbMnpLtrX72RCkPW1ffLq', '71vlXMJNXvoCeuejiRw=', '=wJNM', - '-----END PGP PRIVATE KEY BLOCK-----'].join('\n'); + '-----END PGP PRIVATE KEY BLOCK-----', + ''].join('\t \r\n'); const result = await openpgp.key.readArmored(privKey); expect(result.err).to.not.exist; diff --git a/test/general/key.js b/test/general/key.js index e9d8a803..1275f2f3 100644 --- a/test/general/key.js +++ b/test/general/key.js @@ -841,19 +841,19 @@ const wrong_key = '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'); const expiredKey = - `-----BEGIN PGP PRIVATE KEY BLOCK----- +`-----BEGIN PGP PRIVATE KEY BLOCK----- - xcA4BAAAAAEBAgCgONc0J8rfO6cJw5YTP38x1ze2tAYIO7EcmRCNYwMkXngb - 0Qdzg34Q5RW0rNiR56VB6KElPUhePRPVklLFiIvHABEBAAEAAf9qabYMzsz/ - /LeRVZSsTgTljmJTdzd2ambUbpi+vt8MXJsbaWh71vjoLMWSXajaKSPDjVU5 - waFNt9kLqwGGGLqpAQD5ZdMH2XzTq6GU9Ka69iZs6Pbnzwdz59Vc3i8hXlUj - zQEApHargCTsrtvSrm+hK/pN51/BHAy9lxCAw9f2etx+AeMA/RGrijkFZtYt - jeWdv/usXL3mgHvEcJv63N5zcEvDX5X4W1bND3Rlc3QxIDxhQGIuY29tPsJ7 - BBABCAAvBQIAAAABBQMAAAU5BgsJBwgDAgkQzcF99nGrkAkEFQgKAgMWAgEC - GQECGwMCHgEAABAlAfwPehmLZs+gOhOTTaSslqQ50bl/REjmv42Nyr1ZBlQS - DECl1Qu4QyeXin29uEXWiekMpNlZVsEuc8icCw6ABhIZ - =/7PI - -----END PGP PRIVATE KEY BLOCK-----`; +xcA4BAAAAAEBAgCgONc0J8rfO6cJw5YTP38x1ze2tAYIO7EcmRCNYwMkXngb +0Qdzg34Q5RW0rNiR56VB6KElPUhePRPVklLFiIvHABEBAAEAAf9qabYMzsz/ +/LeRVZSsTgTljmJTdzd2ambUbpi+vt8MXJsbaWh71vjoLMWSXajaKSPDjVU5 +waFNt9kLqwGGGLqpAQD5ZdMH2XzTq6GU9Ka69iZs6Pbnzwdz59Vc3i8hXlUj +zQEApHargCTsrtvSrm+hK/pN51/BHAy9lxCAw9f2etx+AeMA/RGrijkFZtYt +jeWdv/usXL3mgHvEcJv63N5zcEvDX5X4W1bND3Rlc3QxIDxhQGIuY29tPsJ7 +BBABCAAvBQIAAAABBQMAAAU5BgsJBwgDAgkQzcF99nGrkAkEFQgKAgMWAgEC +GQECGwMCHgEAABAlAfwPehmLZs+gOhOTTaSslqQ50bl/REjmv42Nyr1ZBlQS +DECl1Qu4QyeXin29uEXWiekMpNlZVsEuc8icCw6ABhIZ +=/7PI +-----END PGP PRIVATE KEY BLOCK-----`; const multipleBindingSignatures = `-----BEGIN PGP PUBLIC KEY BLOCK-----