Fix armor errors

Also, fix error handling in transformPair()
This commit is contained in:
Daniel Huigens 2018-06-13 16:27:59 +02:00
parent 304cbf4783
commit 55fd292fba
5 changed files with 149 additions and 132 deletions

View File

@ -212,57 +212,69 @@ function dearmor(input) {
let headersDone; let headersDone;
let text = []; let text = [];
let textDone; let textDone;
let resolved = false;
let checksum; let checksum;
let data = base64.decode(stream.transformPair(input, async (readable, writable) => { let data = base64.decode(stream.transformPair(input, async (readable, writable) => {
const reader = stream.getReader(readable); const reader = stream.getReader(readable);
const writer = stream.getWriter(writable); const writer = stream.getWriter(writable);
while (true) { while (true) {
await writer.ready; if (resolved) await writer.ready;
let line = await reader.readLine(); try {
if (line === undefined) { let line = await reader.readLine();
writer.abort('Misformed armored text'); if (line === undefined) {
break; throw new Error('Misformed armored text');
}
// remove trailing whitespace at end of lines
// remove leading whitespace for compat with older versions of OpenPGP.js
line = line.trim();
if (!type) {
if (reSplit.test(line)) {
type = getType(line);
} }
} else if (!headersDone) { // remove trailing whitespace at end of lines
if (reSplit.test(line)) { // remove leading whitespace for compat with older versions of OpenPGP.js
reject(new Error('Mandatory blank line missing between armor headers and armor data')); line = line.trim();
} if (!type) {
if (!reEmptyLine.test(line)) { if (reSplit.test(line)) {
lastHeaders.push(line); type = getType(line);
} else { }
verifyHeaders(lastHeaders); } else if (!headersDone) {
headersDone = true; if (reSplit.test(line)) {
if (textDone || type !== 2) resolve({ text, data, headers, type }); reject(new Error('Mandatory blank line missing between armor headers and armor data'));
} }
} else if (!textDone && type === 2) { if (!reEmptyLine.test(line)) {
if (!reSplit.test(line)) { lastHeaders.push(line);
// Reverse dash-escaping for msg
text.push(line.replace(/^- /, ''));
} else {
text = text.join('\r\n');
textDone = true;
verifyHeaders(lastHeaders);
lastHeaders = [];
headersDone = false;
}
} else {
if (!reSplit.test(line)) {
if (line[0] !== '=') {
writer.write(line);
} else { } else {
checksum = line.substr(1); verifyHeaders(lastHeaders);
headersDone = true;
if (textDone || type !== 2) {
resolve({ text, data, headers, type });
resolved = true;
}
}
} else if (!textDone && type === 2) {
if (!reSplit.test(line)) {
// Reverse dash-escaping for msg
text.push(line.replace(/^- /, ''));
} else {
text = text.join('\r\n');
textDone = true;
verifyHeaders(lastHeaders);
lastHeaders = [];
headersDone = false;
} }
} else { } else {
writer.close(); if (!reSplit.test(line)) {
break; 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;
} }
} }
})); }));
@ -275,10 +287,10 @@ function dearmor(input) {
const writer = stream.getWriter(writable); const writer = stream.getWriter(writable);
await writer.ready; await writer.ready;
if (checksum !== checksumVerifiedString && (checksum || config.checksum_required)) { if (checksum !== checksumVerifiedString && (checksum || config.checksum_required)) {
writer.abort(new Error("Ascii armor integrity check on message failed: '" + checksum + "' should be '" + await writer.abort(new Error("Ascii armor integrity check on message failed: '" + checksum + "' should be '" +
checksumVerifiedString + "'")); checksumVerifiedString + "'"));
} else { } else {
writer.close(); await writer.close();
} }
}); });
} catch(e) { } catch(e) {

View File

@ -38,31 +38,35 @@ function List() {
List.prototype.read = async function (bytes) { List.prototype.read = async function (bytes) {
this.stream = stream.transformPair(bytes, async (readable, writable) => { this.stream = stream.transformPair(bytes, async (readable, writable) => {
const writer = stream.getWriter(writable); const writer = stream.getWriter(writable);
while (true) { try {
await writer.ready; while (true) {
const done = await packetParser.read(readable, async parsed => {
try {
const tag = enums.read(enums.packet, parsed.tag);
const packet = packets.newPacketFromTag(tag);
packet.packets = new List();
packet.fromStream = util.isStream(parsed.packet);
await packet.read(parsed.packet);
await writer.write(packet);
} catch (e) {
if (!config.tolerant ||
parsed.tag === enums.packet.symmetricallyEncrypted ||
parsed.tag === enums.packet.literal ||
parsed.tag === enums.packet.compressed) {
writer.abort(e);
}
util.print_debug_error(e);
}
});
if (done) {
await writer.ready; await writer.ready;
writer.close(); const done = await packetParser.read(readable, async parsed => {
return; try {
const tag = enums.read(enums.packet, parsed.tag);
const packet = packets.newPacketFromTag(tag);
packet.packets = new List();
packet.fromStream = util.isStream(parsed.packet);
await packet.read(parsed.packet);
await writer.write(packet);
} catch (e) {
if (!config.tolerant ||
parsed.tag === enums.packet.symmetricallyEncrypted ||
parsed.tag === enums.packet.literal ||
parsed.tag === enums.packet.compressed) {
await writer.abort(e);
}
util.print_debug_error(e);
}
});
if (done) {
await writer.ready;
await writer.close();
return;
}
} }
} catch(e) {
await writer.abort(e);
} }
}); });

View File

@ -147,41 +147,45 @@ SymEncryptedAEADProtected.prototype.crypt = async function (fn, key, data) {
return stream.transformPair(data, async (readable, writable) => { return stream.transformPair(data, async (readable, writable) => {
const reader = stream.getReader(readable); const reader = stream.getReader(readable);
const writer = stream.getWriter(writable); const writer = stream.getWriter(writable);
while (true) { try {
await writer.ready; while (true) {
let chunk = await reader.readBytes(chunkSize + tagLengthIfDecrypting) || new Uint8Array(); await writer.ready;
const finalChunk = chunk.subarray(chunk.length - tagLengthIfDecrypting); let chunk = await reader.readBytes(chunkSize + tagLengthIfDecrypting) || new Uint8Array();
chunk = chunk.subarray(0, chunk.length - tagLengthIfDecrypting); const finalChunk = chunk.subarray(chunk.length - tagLengthIfDecrypting);
let cryptedPromise; chunk = chunk.subarray(0, chunk.length - tagLengthIfDecrypting);
let done; let cryptedPromise;
if (!chunkIndex || chunk.length) { let done;
reader.unshift(finalChunk); if (!chunkIndex || chunk.length) {
cryptedPromise = modeInstance[fn](chunk, mode.getNonce(iv, chunkIndexArray), adataArray); reader.unshift(finalChunk);
} else { cryptedPromise = modeInstance[fn](chunk, mode.getNonce(iv, chunkIndexArray), adataArray);
// After the last chunk, we either encrypt a final, empty } else {
// data chunk to get the final authentication tag or // After the last chunk, we either encrypt a final, empty
// validate that final authentication tag. // data chunk to get the final authentication tag or
adataView.setInt32(13 + 4, cryptedBytes); // Should be setInt64(13, ...) // validate that final authentication tag.
cryptedPromise = modeInstance[fn](finalChunk, mode.getNonce(iv, chunkIndexArray), adataTagArray); adataView.setInt32(13 + 4, cryptedBytes); // Should be setInt64(13, ...)
done = true; cryptedPromise = modeInstance[fn](finalChunk, mode.getNonce(iv, chunkIndexArray), adataTagArray);
} done = true;
cryptedBytes += chunk.length - tagLengthIfDecrypting; }
queuedBytes += chunk.length - tagLengthIfDecrypting; cryptedBytes += chunk.length - tagLengthIfDecrypting;
// eslint-disable-next-line no-loop-func queuedBytes += chunk.length - tagLengthIfDecrypting;
latestPromise = latestPromise.then(() => cryptedPromise).then(crypted => { // eslint-disable-next-line no-loop-func
writer.write(crypted); latestPromise = latestPromise.then(() => cryptedPromise).then(async crypted => {
queuedBytes -= chunk.length; await writer.write(crypted);
}).catch(err => writer.abort(err)); queuedBytes -= chunk.length;
// console.log(fn, done, queuedBytes, writer.desiredSize); }).catch(err => writer.abort(err));
if (done || queuedBytes > writer.desiredSize) { // console.log(fn, done, queuedBytes, writer.desiredSize);
await latestPromise; // Respect backpressure if (done || queuedBytes > writer.desiredSize) {
} await latestPromise; // Respect backpressure
if (!done) { }
adataView.setInt32(5 + 4, ++chunkIndex); // Should be setInt64(5, ...) if (!done) {
} else { adataView.setInt32(5 + 4, ++chunkIndex); // Should be setInt64(5, ...)
writer.close(); } else {
break; await writer.close();
break;
}
} }
} catch(e) {
await writer.abort(e);
} }
}); });
} else { } else {

View File

@ -53,7 +53,7 @@ async function pipe(input, target, options) {
} }
writer.releaseLock(); writer.releaseLock();
} }
return input.pipeTo(target, options); return input.pipeTo(target, options).catch(function() {});
} }
function transformRaw(input, options) { function transformRaw(input, options) {
@ -125,22 +125,13 @@ function transformPair(input, fn) {
} }
}); });
const canceledErr = new Error('Readable side was canceled.'); const pipeDonePromise = pipe(input, incoming.writable);
const pipeDonePromise = pipe(input, incoming.writable).catch(e => {
if (e !== canceledErr) {
throw e;
}
});
const outgoing = transformWithCancel(async function() { const outgoing = transformWithCancel(async function() {
incomingTransformController.error(canceledErr); incomingTransformController.error(new Error('Readable side was canceled.'));
await pipeDonePromise; await pipeDonePromise;
}); });
Promise.resolve(fn(incoming.readable, outgoing.writable)).catch(e => { fn(incoming.readable, outgoing.writable);
if (e !== canceledErr) {
throw e;
}
});
return outgoing.readable; return outgoing.readable;
} }
@ -183,16 +174,20 @@ function passiveClone(input) {
const transformed = transformPair(input, async (readable, writable) => { const transformed = transformPair(input, async (readable, writable) => {
const reader = getReader(readable); const reader = getReader(readable);
const writer = getWriter(writable); const writer = getWriter(writable);
while (true) { try {
await writer.ready; while (true) {
const { done, value } = await reader.read(); await writer.ready;
if (done) { const { done, value } = await reader.read();
try { controller.close(); } catch(e) {} if (done) {
await writer.close(); try { controller.close(); } catch(e) {}
return; await writer.close();
return;
}
try { controller.enqueue(value); } catch(e) {}
await writer.write(value);
} }
try { controller.enqueue(value); } catch(e) {} } catch(e) {
await writer.write(value); await writer.abort(e);
} }
}); });
overwrite(input, transformed); overwrite(input, transformed);

View File

@ -416,11 +416,7 @@ describe('Streaming', function() {
let plaintext = []; let plaintext = [];
let i = 0; let i = 0;
let canceled = false; let canceled = false;
let controller;
const data = new ReadableStream({ const data = new ReadableStream({
start(_controller) {
controller = _controller;
},
async pull(controller) { async pull(controller) {
await new Promise(setTimeout); await new Promise(setTimeout);
if (i++ < 10) { if (i++ < 10) {
@ -435,16 +431,22 @@ describe('Streaming', function() {
canceled = true; canceled = true;
} }
}); });
data.controller = controller;
const transformed = stream.transformPair(stream.slice(data, 0, 5000), async (readable, writable) => { const transformed = stream.transformPair(stream.slice(data, 0, 5000), async (readable, writable) => {
const reader = stream.getReader(readable); const reader = stream.getReader(readable);
const writer = stream.getWriter(writable); const writer = stream.getWriter(writable);
while (true) { try {
await writer.ready; while (true) {
const { done, value } = await reader.read(); await writer.ready;
if (done) return writer.close(); const { done, value } = await reader.read();
writer.write(value); if (done) {
await writer.close();
break;
}
await writer.write(value);
}
} catch(e) {
await writer.abort(e);
} }
}); });
await new Promise(resolve => setTimeout(resolve)); await new Promise(resolve => setTimeout(resolve));