Fix detached signing of messages created from streams (#887)

This commit is contained in:
Daniel Huigens 2019-04-29 13:45:09 +02:00 committed by GitHub
parent 038d8466fe
commit 7fb2901ede
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 158 additions and 119 deletions

View File

@ -437,6 +437,14 @@ export function sign({ message, privateKeys, armor=true, streaming=message&&mess
if (detached) { if (detached) {
const signature = await message.signDetached(privateKeys, undefined, date, fromUserIds); const signature = await message.signDetached(privateKeys, undefined, date, fromUserIds);
result.signature = armor ? signature.armor() : signature; result.signature = armor ? signature.armor() : signature;
if (message.packets) {
result.signature = stream.transformPair(message.packets.write(), async (readable, writable) => {
await Promise.all([
stream.pipe(result.signature, writable),
stream.readToEnd(readable).catch(() => {})
]);
});
}
} else { } else {
message = await message.sign(privateKeys, undefined, date, fromUserIds); message = await message.sign(privateKeys, undefined, date, fromUserIds);
if (armor) { if (armor) {

View File

@ -76,7 +76,7 @@ const priv_key =
const passphrase = 'hello world'; const passphrase = 'hello world';
let plaintext, data, i, canceled, expectedType, dataArrived; let privKey, pubKey, plaintext, data, i, canceled, expectedType, dataArrived;
function tests() { function tests() {
it('Encrypt small message', async function() { it('Encrypt small message', async function() {
@ -134,9 +134,6 @@ function tests() {
}); });
it('Sign: Input stream should be canceled when canceling encrypted stream', async function() { it('Sign: Input stream should be canceled when canceling encrypted stream', async function() {
const privKey = (await openpgp.key.readArmored(priv_key)).keys[0];
await privKey.decrypt(passphrase);
const signed = await openpgp.sign({ const signed = await openpgp.sign({
message: openpgp.message.fromBinary(data), message: openpgp.message.fromBinary(data),
privateKeys: privKey privateKeys: privKey
@ -202,10 +199,6 @@ function tests() {
let allow_unauthenticated_streamValue = openpgp.config.allow_unauthenticated_stream; let allow_unauthenticated_streamValue = openpgp.config.allow_unauthenticated_stream;
openpgp.config.allow_unauthenticated_stream = true; openpgp.config.allow_unauthenticated_stream = true;
try { try {
const pubKey = (await openpgp.key.readArmored(pub_key)).keys[0];
const privKey = (await openpgp.key.readArmored(priv_key)).keys[0];
await privKey.decrypt(passphrase);
const encrypted = await openpgp.encrypt({ const encrypted = await openpgp.encrypt({
message: openpgp.message.fromBinary(data), message: openpgp.message.fromBinary(data),
publicKeys: pubKey, publicKeys: pubKey,
@ -268,10 +261,6 @@ function tests() {
let allow_unauthenticated_streamValue = openpgp.config.allow_unauthenticated_stream; let allow_unauthenticated_streamValue = openpgp.config.allow_unauthenticated_stream;
openpgp.config.allow_unauthenticated_stream = true; openpgp.config.allow_unauthenticated_stream = true;
try { try {
const pubKey = (await openpgp.key.readArmored(pub_key)).keys[0];
const privKey = (await openpgp.key.readArmored(priv_key)).keys[0];
await privKey.decrypt(passphrase);
const encrypted = await openpgp.encrypt({ const encrypted = await openpgp.encrypt({
message: openpgp.message.fromBinary(data), message: openpgp.message.fromBinary(data),
publicKeys: pubKey, publicKeys: pubKey,
@ -307,10 +296,6 @@ function tests() {
let allow_unauthenticated_streamValue = openpgp.config.allow_unauthenticated_stream; let allow_unauthenticated_streamValue = openpgp.config.allow_unauthenticated_stream;
openpgp.config.allow_unauthenticated_stream = true; openpgp.config.allow_unauthenticated_stream = true;
try { try {
const pubKey = (await openpgp.key.readArmored(pub_key)).keys[0];
const privKey = (await openpgp.key.readArmored(priv_key)).keys[0];
await privKey.decrypt(passphrase);
const encrypted = await openpgp.encrypt({ const encrypted = await openpgp.encrypt({
message: openpgp.message.fromBinary(data), message: openpgp.message.fromBinary(data),
publicKeys: pubKey, publicKeys: pubKey,
@ -342,40 +327,30 @@ function tests() {
} }
}); });
it('Sign/verify: Detect armor checksum error (allow_unauthenticated_stream=true)', async function() { it('Sign/verify: Detect armor checksum error', async function() {
let allow_unauthenticated_streamValue = openpgp.config.allow_unauthenticated_stream; const signed = await openpgp.sign({
openpgp.config.allow_unauthenticated_stream = true; message: openpgp.message.fromBinary(data),
try { privateKeys: privKey
const pubKey = (await openpgp.key.readArmored(pub_key)).keys[0]; });
const privKey = (await openpgp.key.readArmored(priv_key)).keys[0];
await privKey.decrypt(passphrase);
const signed = await openpgp.sign({ const msgAsciiArmored = signed.data;
message: openpgp.message.fromBinary(data), const message = await openpgp.message.readArmored(openpgp.stream.transform(msgAsciiArmored, value => {
privateKeys: privKey value += '';
}); const newlineIndex = value.indexOf('\r\n', 500);
if (value.length > 1000) return value.slice(0, newlineIndex - 1) + (value[newlineIndex - 1] === 'a' ? 'b' : 'a') + value.slice(newlineIndex);
const msgAsciiArmored = signed.data; return value;
const message = await openpgp.message.readArmored(openpgp.stream.transform(msgAsciiArmored, value => { }));
value += ''; const verified = await openpgp.verify({
const newlineIndex = value.indexOf('\r\n', 500); publicKeys: pubKey,
if (value.length > 1000) return value.slice(0, newlineIndex - 1) + (value[newlineIndex - 1] === 'a' ? 'b' : 'a') + value.slice(newlineIndex); message,
return value; streaming: expectedType
})); });
const verified = await openpgp.verify({ expect(util.isStream(verified.data)).to.equal(expectedType);
publicKeys: pubKey, const reader = openpgp.stream.getReader(verified.data);
message, expect(await reader.peekBytes(1024)).not.to.deep.equal(plaintext[0]);
streaming: expectedType dataArrived();
}); await expect(reader.readToEnd()).to.be.rejectedWith('Ascii armor integrity check on message failed');
expect(util.isStream(verified.data)).to.equal(expectedType); expect(verified.signatures).to.exist.and.have.length(1);
const reader = openpgp.stream.getReader(verified.data);
expect(await reader.peekBytes(1024)).not.to.deep.equal(plaintext[0]);
dataArrived();
await expect(reader.readToEnd()).to.be.rejectedWith('Ascii armor integrity check on message failed');
expect(verified.signatures).to.exist.and.have.length(1);
} finally {
openpgp.config.allow_unauthenticated_stream = allow_unauthenticated_streamValue;
}
}); });
it('Encrypt and decrypt larger message roundtrip (draft04)', async function() { it('Encrypt and decrypt larger message roundtrip (draft04)', async function() {
@ -505,40 +480,27 @@ function tests() {
} }
}); });
it('Sign/verify: Input stream should be canceled when canceling decrypted stream (draft04)', async function() { it('Sign/verify: Input stream should be canceled when canceling verified stream', async function() {
let aead_protectValue = openpgp.config.aead_protect; const signed = await openpgp.sign({
let aead_chunk_size_byteValue = openpgp.config.aead_chunk_size_byte; message: openpgp.message.fromBinary(data),
openpgp.config.aead_protect = true; privateKeys: privKey
openpgp.config.aead_chunk_size_byte = 4; });
try {
const pubKey = (await openpgp.key.readArmored(pub_key)).keys[0];
const privKey = (await openpgp.key.readArmored(priv_key)).keys[0];
await privKey.decrypt(passphrase);
const signed = await openpgp.sign({ const msgAsciiArmored = signed.data;
message: openpgp.message.fromBinary(data), const message = await openpgp.message.readArmored(msgAsciiArmored);
privateKeys: privKey const verified = await openpgp.verify({
}); publicKeys: pubKey,
message
const msgAsciiArmored = signed.data; });
const message = await openpgp.message.readArmored(msgAsciiArmored); expect(util.isStream(verified.data)).to.equal(expectedType);
const verified = await openpgp.verify({ const reader = openpgp.stream.getReader(verified.data);
publicKeys: pubKey, expect(await reader.readBytes(1024)).to.deep.equal(plaintext[0]);
message dataArrived();
}); reader.releaseLock();
expect(util.isStream(verified.data)).to.equal(expectedType); await openpgp.stream.cancel(verified.data, new Error('canceled by test'));
const reader = openpgp.stream.getReader(verified.data); expect(canceled).to.be.true;
expect(await reader.readBytes(1024)).to.deep.equal(plaintext[0]); expect(verified.signatures).to.exist.and.have.length(1);
dataArrived(); await expect(verified.signatures[0].verified).to.be.rejectedWith('canceled');
reader.releaseLock();
await openpgp.stream.cancel(verified.data, new Error('canceled by test'));
expect(canceled).to.be.true;
expect(verified.signatures).to.exist.and.have.length(1);
await expect(verified.signatures[0].verified).to.be.rejectedWith('canceled');
} finally {
openpgp.config.aead_protect = aead_protectValue;
openpgp.config.aead_chunk_size_byte = aead_chunk_size_byteValue;
}
}); });
it("Don't pull entire input stream when we're not pulling encrypted stream", async function() { it("Don't pull entire input stream when we're not pulling encrypted stream", async function() {
@ -550,14 +512,10 @@ function tests() {
expect(await reader.readBytes(1024)).to.match(/^-----BEGIN PGP MESSAGE-----\r\n/); expect(await reader.readBytes(1024)).to.match(/^-----BEGIN PGP MESSAGE-----\r\n/);
dataArrived(); dataArrived();
await new Promise(resolve => setTimeout(resolve, 3000)); await new Promise(resolve => setTimeout(resolve, 3000));
expect(i).to.be.lessThan(50); expect(i).to.be.lessThan(expectedType === 'web' ? 50 : 100);
}); });
it("Sign: Don't pull entire input stream when we're not pulling signed stream", async function() { it("Sign: Don't pull entire input stream when we're not pulling signed stream", async function() {
const pubKey = (await openpgp.key.readArmored(pub_key)).keys[0];
const privKey = (await openpgp.key.readArmored(priv_key)).keys[0];
await privKey.decrypt(passphrase);
const signed = await openpgp.sign({ const signed = await openpgp.sign({
message: openpgp.message.fromBinary(data), message: openpgp.message.fromBinary(data),
privateKeys: privKey privateKeys: privKey
@ -566,7 +524,7 @@ function tests() {
expect(await reader.readBytes(1024)).to.match(/^-----BEGIN PGP MESSAGE-----\r\n/); expect(await reader.readBytes(1024)).to.match(/^-----BEGIN PGP MESSAGE-----\r\n/);
dataArrived(); dataArrived();
await new Promise(resolve => setTimeout(resolve, 3000)); await new Promise(resolve => setTimeout(resolve, 3000));
expect(i).to.be.lessThan(50); expect(i).to.be.lessThan(expectedType === 'web' ? 50 : 100);
}); });
it("Don't pull entire input stream when we're not pulling decrypted stream (draft04)", async function() { it("Don't pull entire input stream when we're not pulling decrypted stream (draft04)", async function() {
@ -593,7 +551,7 @@ function tests() {
expect(await reader.readBytes(1024)).to.deep.equal(plaintext[0]); expect(await reader.readBytes(1024)).to.deep.equal(plaintext[0]);
dataArrived(); dataArrived();
await new Promise(resolve => setTimeout(resolve, 3000)); await new Promise(resolve => setTimeout(resolve, 3000));
expect(i).to.be.lessThan(50); expect(i).to.be.lessThan(expectedType === 'web' ? 50 : 100);
} finally { } finally {
openpgp.config.aead_protect = aead_protectValue; openpgp.config.aead_protect = aead_protectValue;
openpgp.config.aead_chunk_size_byte = aead_chunk_size_byteValue; openpgp.config.aead_chunk_size_byte = aead_chunk_size_byteValue;
@ -601,36 +559,103 @@ function tests() {
} }
}); });
it("Sign/verify: Don't pull entire input stream when we're not pulling verified stream (draft04)", async function() { it("Sign/verify: Don't pull entire input stream when we're not pulling verified stream", async function() {
let aead_protectValue = openpgp.config.aead_protect; const signed = await openpgp.sign({
let aead_chunk_size_byteValue = openpgp.config.aead_chunk_size_byte; message: openpgp.message.fromBinary(data),
openpgp.config.aead_protect = true; privateKeys: privKey
openpgp.config.aead_chunk_size_byte = 4; });
try { const msgAsciiArmored = signed.data;
const pubKey = (await openpgp.key.readArmored(pub_key)).keys[0]; const message = await openpgp.message.readArmored(msgAsciiArmored);
const privKey = (await openpgp.key.readArmored(priv_key)).keys[0]; const verified = await openpgp.verify({
await privKey.decrypt(passphrase); publicKeys: pubKey,
message
});
expect(util.isStream(verified.data)).to.equal(expectedType);
const reader = openpgp.stream.getReader(verified.data);
expect(await reader.readBytes(1024)).to.deep.equal(plaintext[0]);
dataArrived();
await new Promise(resolve => setTimeout(resolve, 3000));
expect(i).to.be.lessThan(expectedType === 'web' ? 50 : 100);
});
const signed = await openpgp.sign({ it('Detached sign small message', async function() {
message: openpgp.message.fromBinary(data), dataArrived(); // Do not wait until data arrived.
privateKeys: privKey const data = new ReadableStream({
}); async start(controller) {
const msgAsciiArmored = signed.data; controller.enqueue(util.str_to_Uint8Array('hello '));
const message = await openpgp.message.readArmored(msgAsciiArmored); controller.enqueue(util.str_to_Uint8Array('world'));
const verified = await openpgp.verify({ controller.close();
publicKeys: pubKey, }
message });
}); const signed = await openpgp.sign({
expect(util.isStream(verified.data)).to.equal(expectedType); message: openpgp.message.fromBinary(data),
const reader = openpgp.stream.getReader(verified.data); privateKeys: privKey,
expect(await reader.readBytes(1024)).to.deep.equal(plaintext[0]); detached: true,
dataArrived(); });
await new Promise(resolve => setTimeout(resolve, 3000)); const sigArmored = await openpgp.stream.readToEnd(signed.signature);
expect(i).to.be.lessThan(50); const signature = await openpgp.message.readArmored(sigArmored);
} finally { const verified = await openpgp.verify({
openpgp.config.aead_protect = aead_protectValue; signature,
openpgp.config.aead_chunk_size_byte = aead_chunk_size_byteValue; publicKeys: pubKey,
} message: openpgp.message.fromText('hello world')
});
expect(openpgp.util.decode_utf8(verified.data)).to.equal('hello world');
expect(verified.signatures).to.exist.and.have.length(1);
expect(verified.signatures[0].valid).to.be.true;
});
it('Detached sign small message (not streaming)', async function() {
dataArrived(); // Do not wait until data arrived.
const data = new ReadableStream({
async start(controller) {
controller.enqueue(util.str_to_Uint8Array('hello '));
controller.enqueue(util.str_to_Uint8Array('world'));
controller.close();
}
});
const signed = await openpgp.sign({
message: openpgp.message.fromBinary(data),
privateKeys: privKey,
detached: true,
streaming: false,
});
const sigArmored = await openpgp.stream.readToEnd(signed.signature);
const signature = await openpgp.message.readArmored(sigArmored);
const verified = await openpgp.verify({
signature,
publicKeys: pubKey,
message: openpgp.message.fromText('hello world')
});
expect(openpgp.util.decode_utf8(verified.data)).to.equal('hello world');
expect(verified.signatures).to.exist.and.have.length(1);
expect(verified.signatures[0].valid).to.be.true;
});
it("Detached sign is expected to pull entire input stream when we're not pulling signed stream", async function() {
const signed = await openpgp.sign({
message: openpgp.message.fromBinary(data),
privateKeys: privKey,
detached: true
});
const reader = openpgp.stream.getReader(signed.signature);
expect((await reader.readBytes(31)).toString('utf8')).to.equal('-----BEGIN PGP SIGNATURE-----\r\n');
dataArrived();
await new Promise(resolve => setTimeout(resolve, 1000));
expect(i).to.be.greaterThan(100);
});
it('Detached sign: Input stream should be canceled when canceling signed stream', async function() {
const signed = await openpgp.sign({
message: openpgp.message.fromBinary(data),
privateKeys: privKey,
detached: true
});
const reader = openpgp.stream.getReader(signed.signature);
expect((await reader.readBytes(31)).toString('utf8')).to.equal('-----BEGIN PGP SIGNATURE-----\r\n');
dataArrived();
reader.releaseLock();
await openpgp.stream.cancel(signed.signature, new Error('canceled by test'));
expect(canceled).to.be.true;
}); });
if (openpgp.util.detectNode()) { if (openpgp.util.detectNode()) {
@ -662,6 +687,12 @@ function tests() {
describe('Streaming', function() { describe('Streaming', function() {
let currentTest = 0; let currentTest = 0;
before(async function() {
pubKey = (await openpgp.key.readArmored(pub_key)).keys[0];
privKey = (await openpgp.key.readArmored(priv_key)).keys[0];
await privKey.decrypt(passphrase);
});
beforeEach(function() { beforeEach(function() {
let test = ++currentTest; let test = ++currentTest;
@ -674,7 +705,7 @@ describe('Streaming', function() {
data = new ReadableStream({ data = new ReadableStream({
async pull(controller) { async pull(controller) {
await new Promise(setTimeout); await new Promise(setTimeout);
if (test === currentTest && i++ < 10) { if (test === currentTest && i++ < 100) {
if (i === 4) await dataArrivedPromise; if (i === 4) await dataArrivedPromise;
let randomBytes = await openpgp.crypto.random.getRandomBytes(1024); let randomBytes = await openpgp.crypto.random.getRandomBytes(1024);
controller.enqueue(randomBytes); controller.enqueue(randomBytes);