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,14 +327,7 @@ 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;
openpgp.config.allow_unauthenticated_stream = true;
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 signed = await openpgp.sign({
message: openpgp.message.fromBinary(data), message: openpgp.message.fromBinary(data),
privateKeys: privKey privateKeys: privKey
@ -373,9 +351,6 @@ function tests() {
dataArrived(); dataArrived();
await expect(reader.readToEnd()).to.be.rejectedWith('Ascii armor integrity check on message failed'); await expect(reader.readToEnd()).to.be.rejectedWith('Ascii armor integrity check on message failed');
expect(verified.signatures).to.exist.and.have.length(1); 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,16 +480,7 @@ 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;
let aead_chunk_size_byteValue = openpgp.config.aead_chunk_size_byte;
openpgp.config.aead_protect = true;
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 signed = await openpgp.sign({
message: openpgp.message.fromBinary(data), message: openpgp.message.fromBinary(data),
privateKeys: privKey privateKeys: privKey
@ -535,10 +501,6 @@ function tests() {
expect(canceled).to.be.true; expect(canceled).to.be.true;
expect(verified.signatures).to.exist.and.have.length(1); expect(verified.signatures).to.exist.and.have.length(1);
await expect(verified.signatures[0].verified).to.be.rejectedWith('canceled'); 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,16 +559,7 @@ 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;
let aead_chunk_size_byteValue = openpgp.config.aead_chunk_size_byte;
openpgp.config.aead_protect = true;
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 signed = await openpgp.sign({
message: openpgp.message.fromBinary(data), message: openpgp.message.fromBinary(data),
privateKeys: privKey privateKeys: privKey
@ -626,12 +575,88 @@ 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 { });
openpgp.config.aead_protect = aead_protectValue;
openpgp.config.aead_chunk_size_byte = aead_chunk_size_byteValue; it('Detached sign small message', 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,
});
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 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()) {
const fs = util.nodeRequire('fs'); const fs = util.nodeRequire('fs');
@ -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);