Rename asStream to streaming

Also, break up `postProcess`.
This commit is contained in:
Daniel Huigens 2018-07-17 17:26:06 +02:00
parent b35b167e63
commit 0ddff3ae7d
3 changed files with 77 additions and 67 deletions

View File

@ -99,7 +99,7 @@ Message.prototype.getSigningKeyIds = function() {
* @returns {Promise<Message>} new message with decrypted content * @returns {Promise<Message>} new message with decrypted content
* @async * @async
*/ */
Message.prototype.decrypt = async function(privateKeys, passwords, sessionKeys, asStream) { Message.prototype.decrypt = async function(privateKeys, passwords, sessionKeys, streaming) {
const keyObjs = sessionKeys || await this.decryptSessionKeys(privateKeys, passwords); const keyObjs = sessionKeys || await this.decryptSessionKeys(privateKeys, passwords);
const symEncryptedPacketlist = this.packets.filterByTag( const symEncryptedPacketlist = this.packets.filterByTag(
@ -120,7 +120,7 @@ Message.prototype.decrypt = async function(privateKeys, passwords, sessionKeys,
} }
try { try {
await symEncryptedPacket.decrypt(keyObjs[i].algorithm, keyObjs[i].data, asStream); await symEncryptedPacket.decrypt(keyObjs[i].algorithm, keyObjs[i].data, streaming);
break; break;
} catch (e) { } catch (e) {
util.print_debug_error(e); util.print_debug_error(e);
@ -260,7 +260,7 @@ Message.prototype.getText = function() {
* @returns {Promise<Message>} new message with encrypted content * @returns {Promise<Message>} new message with encrypted content
* @async * @async
*/ */
Message.prototype.encrypt = async function(keys, passwords, sessionKey, wildcard=false, date=new Date(), userId={}, asStream) { Message.prototype.encrypt = async function(keys, passwords, sessionKey, wildcard=false, date=new Date(), userId={}, streaming) {
let symAlgo; let symAlgo;
let aeadAlgo; let aeadAlgo;
let symEncryptedPacket; let symEncryptedPacket;
@ -300,7 +300,7 @@ Message.prototype.encrypt = async function(keys, passwords, sessionKey, wildcard
} }
symEncryptedPacket.packets = this.packets; symEncryptedPacket.packets = this.packets;
await symEncryptedPacket.encrypt(symAlgo, sessionKey, asStream); await symEncryptedPacket.encrypt(symAlgo, sessionKey, streaming);
msg.packets.push(symEncryptedPacket); msg.packets.push(symEncryptedPacket);
symEncryptedPacket.packets = new packet.List(); // remove packets after encryption symEncryptedPacket.packets = new packet.List(); // remove packets after encryption
@ -536,7 +536,7 @@ export async function createSignaturePackets(literalDataPacket, privateKeys, sig
* @returns {Promise<Array<({keyid: module:type/keyid, valid: Boolean})>>} list of signer's keyid and validity of signature * @returns {Promise<Array<({keyid: module:type/keyid, valid: Boolean})>>} list of signer's keyid and validity of signature
* @async * @async
*/ */
Message.prototype.verify = async function(keys, date=new Date(), asStream) { Message.prototype.verify = async function(keys, date=new Date(), streaming) {
const msg = this.unwrapCompressed(); const msg = this.unwrapCompressed();
const literalDataList = msg.packets.filterByTag(enums.packet.literal); const literalDataList = msg.packets.filterByTag(enums.packet.literal);
if (literalDataList.length !== 1) { if (literalDataList.length !== 1) {
@ -550,7 +550,7 @@ Message.prototype.verify = async function(keys, date=new Date(), asStream) {
onePassSig.correspondingSigResolve = resolve; onePassSig.correspondingSigResolve = resolve;
}); });
onePassSig.signatureData = stream.fromAsync(async () => (await onePassSig.correspondingSig).signatureData); onePassSig.signatureData = stream.fromAsync(async () => (await onePassSig.correspondingSig).signatureData);
onePassSig.hashed = onePassSig.hash(literalDataList[0], undefined, asStream); onePassSig.hashed = onePassSig.hash(literalDataList[0], undefined, streaming);
}); });
const verificationObjects = await createVerificationObjects(onePassSigList, literalDataList, keys, date); const verificationObjects = await createVerificationObjects(onePassSigList, literalDataList, keys, date);
msg.packets.stream = stream.transformPair(msg.packets.stream, async (readable, writable) => { msg.packets.stream = stream.transformPair(msg.packets.stream, async (readable, writable) => {

View File

@ -281,7 +281,7 @@ export function encryptKey({ privateKey, passphrase }) {
* @param {Object} sessionKey (optional) session key in the form: { data:Uint8Array, algorithm:String } * @param {Object} sessionKey (optional) session key in the form: { data:Uint8Array, algorithm:String }
* @param {module:enums.compression} compression (optional) which compression algorithm to compress the message with, defaults to what is specified in config * @param {module:enums.compression} compression (optional) which compression algorithm to compress the message with, defaults to what is specified in config
* @param {Boolean} armor (optional) if the return values should be ascii armored or the message/signature objects * @param {Boolean} armor (optional) if the return values should be ascii armored or the message/signature objects
* @param {Boolean} asStream (optional) whether to return data as a ReadableStream. Defaults to true if data is a Stream. * @param {Boolean} streaming (optional) whether to return data as a ReadableStream. Defaults to true if data is a Stream.
* @param {Boolean} detached (optional) if the signature should be detached (if true, signature will be added to returned object) * @param {Boolean} detached (optional) if the signature should be detached (if true, signature will be added to returned object)
* @param {Signature} signature (optional) a detached signature to add to the encrypted message * @param {Signature} signature (optional) a detached signature to add to the encrypted message
* @param {Boolean} returnSessionKey (optional) if the unencrypted session key should be added to returned object * @param {Boolean} returnSessionKey (optional) if the unencrypted session key should be added to returned object
@ -295,11 +295,11 @@ export function encryptKey({ privateKey, passphrase }) {
* @async * @async
* @static * @static
*/ */
export function encrypt({ message, publicKeys, privateKeys, passwords, sessionKey, compression=config.compression, armor=true, asStream=message&&message.fromStream, detached=false, signature=null, returnSessionKey=false, wildcard=false, date=new Date(), fromUserId={}, toUserId={} }) { export function encrypt({ message, publicKeys, privateKeys, passwords, sessionKey, compression=config.compression, armor=true, streaming=message&&message.fromStream, detached=false, signature=null, returnSessionKey=false, wildcard=false, date=new Date(), fromUserId={}, toUserId={} }) {
checkMessage(message); publicKeys = toArray(publicKeys); privateKeys = toArray(privateKeys); passwords = toArray(passwords); checkMessage(message); publicKeys = toArray(publicKeys); privateKeys = toArray(privateKeys); passwords = toArray(passwords);
if (!nativeAEAD() && asyncProxy) { // use web worker if web crypto apis are not supported if (!nativeAEAD() && asyncProxy) { // use web worker if web crypto apis are not supported
return asyncProxy.delegate('encrypt', { message, publicKeys, privateKeys, passwords, sessionKey, compression, armor, asStream, detached, signature, returnSessionKey, wildcard, date, fromUserId, toUserId }); return asyncProxy.delegate('encrypt', { message, publicKeys, privateKeys, passwords, sessionKey, compression, armor, streaming, detached, signature, returnSessionKey, wildcard, date, fromUserId, toUserId });
} }
const result = {}; const result = {};
return Promise.resolve().then(async function() { return Promise.resolve().then(async function() {
@ -315,7 +315,7 @@ export function encrypt({ message, publicKeys, privateKeys, passwords, sessionKe
} }
} }
message = message.compress(compression); message = message.compress(compression);
return message.encrypt(publicKeys, passwords, sessionKey, wildcard, date, toUserId, asStream); return message.encrypt(publicKeys, passwords, sessionKey, wildcard, date, toUserId, streaming);
}).then(async encrypted => { }).then(async encrypted => {
if (armor) { if (armor) {
@ -326,7 +326,7 @@ export function encrypt({ message, publicKeys, privateKeys, passwords, sessionKe
if (returnSessionKey) { if (returnSessionKey) {
result.sessionKey = encrypted.sessionKey; result.sessionKey = encrypted.sessionKey;
} }
return convertStreams(result, asStream, armor ? ['signature', 'data'] : []); return convertStreams(result, streaming, armor ? ['signature', 'data'] : []);
}).catch(onError.bind(null, 'Error encrypting message')); }).catch(onError.bind(null, 'Error encrypting message'));
} }
@ -339,7 +339,7 @@ export function encrypt({ message, publicKeys, privateKeys, passwords, sessionKe
* @param {Object|Array<Object>} sessionKeys (optional) session keys in the form: { data:Uint8Array, algorithm:String } * @param {Object|Array<Object>} sessionKeys (optional) session keys in the form: { data:Uint8Array, algorithm:String }
* @param {Key|Array<Key>} publicKeys (optional) array of public keys or single key, to verify signatures * @param {Key|Array<Key>} publicKeys (optional) array of public keys or single key, to verify signatures
* @param {String} format (optional) return data format either as 'utf8' or 'binary' * @param {String} format (optional) return data format either as 'utf8' or 'binary'
* @param {Boolean} asStream (optional) whether to return data as a ReadableStream. Defaults to true if message was created from a Stream. * @param {Boolean} streaming (optional) whether to return data as a ReadableStream. Defaults to true if message was created from a Stream.
* @param {Signature} signature (optional) detached signature for verification * @param {Signature} signature (optional) detached signature for verification
* @param {Date} date (optional) use the given date for verification instead of the current time * @param {Date} date (optional) use the given date for verification instead of the current time
* @returns {Promise<Object>} decrypted and verified message in the form: * @returns {Promise<Object>} decrypted and verified message in the form:
@ -347,23 +347,28 @@ export function encrypt({ message, publicKeys, privateKeys, passwords, sessionKe
* @async * @async
* @static * @static
*/ */
export function decrypt({ message, privateKeys, passwords, sessionKeys, publicKeys, format='utf8', asStream=message&&message.fromStream, signature=null, date=new Date() }) { export function decrypt({ message, privateKeys, passwords, sessionKeys, publicKeys, format='utf8', streaming=message&&message.fromStream, signature=null, date=new Date() }) {
checkMessage(message); publicKeys = toArray(publicKeys); privateKeys = toArray(privateKeys); passwords = toArray(passwords); sessionKeys = toArray(sessionKeys); checkMessage(message); publicKeys = toArray(publicKeys); privateKeys = toArray(privateKeys); passwords = toArray(passwords); sessionKeys = toArray(sessionKeys);
if (!nativeAEAD() && asyncProxy) { // use web worker if web crypto apis are not supported if (!nativeAEAD() && asyncProxy) { // use web worker if web crypto apis are not supported
return asyncProxy.delegate('decrypt', { message, privateKeys, passwords, sessionKeys, publicKeys, format, asStream, signature, date }); return asyncProxy.delegate('decrypt', { message, privateKeys, passwords, sessionKeys, publicKeys, format, streaming, signature, date });
} }
return message.decrypt(privateKeys, passwords, sessionKeys, asStream).then(async function(decrypted) { return message.decrypt(privateKeys, passwords, sessionKeys, streaming).then(async function(decrypted) {
if (!publicKeys) { if (!publicKeys) {
publicKeys = []; publicKeys = [];
} }
const result = {}; const result = {};
result.signatures = signature ? await decrypted.verifyDetached(signature, publicKeys, date) : await decrypted.verify(publicKeys, date, asStream); result.signatures = signature ? await decrypted.verifyDetached(signature, publicKeys, date, streaming) : await decrypted.verify(publicKeys, date, streaming);
result.data = format === 'binary' ? decrypted.getLiteralData() : decrypted.getText(); result.data = format === 'binary' ? decrypted.getLiteralData() : decrypted.getText();
result.data = await convertStream(result.data, streaming);
result.filename = decrypted.getFilename(); result.filename = decrypted.getFilename();
await postProcess(result, asStream, message, decrypted.packets.stream); if (streaming) {
linkStreams(result, message, decrypted.packets.stream);
} else {
await prepareSignatures(result.signatures);
}
return result; return result;
}).catch(onError.bind(null, 'Error decrypting message')); }).catch(onError.bind(null, 'Error decrypting message'));
} }
@ -381,7 +386,7 @@ export function decrypt({ message, privateKeys, passwords, sessionKeys, publicKe
* @param {CleartextMessage | Message} message (cleartext) message to be signed * @param {CleartextMessage | Message} message (cleartext) message to be signed
* @param {Key|Array<Key>} privateKeys array of keys or single key with decrypted secret key data to sign cleartext * @param {Key|Array<Key>} privateKeys array of keys or single key with decrypted secret key data to sign cleartext
* @param {Boolean} armor (optional) if the return value should be ascii armored or the message object * @param {Boolean} armor (optional) if the return value should be ascii armored or the message object
* @param {Boolean} asStream (optional) whether to return data as a ReadableStream. Defaults to true if data is a Stream. * @param {Boolean} streaming (optional) whether to return data as a ReadableStream. Defaults to true if data is a Stream.
* @param {Boolean} detached (optional) if the return value should contain a detached signature * @param {Boolean} detached (optional) if the return value should contain a detached signature
* @param {Date} date (optional) override the creation date of the signature * @param {Date} date (optional) override the creation date of the signature
* @param {Object} fromUserId (optional) user ID to sign with, e.g. { name:'Steve Sender', email:'steve@openpgp.org' } * @param {Object} fromUserId (optional) user ID to sign with, e.g. { name:'Steve Sender', email:'steve@openpgp.org' }
@ -391,13 +396,13 @@ export function decrypt({ message, privateKeys, passwords, sessionKeys, publicKe
* @async * @async
* @static * @static
*/ */
export function sign({ message, privateKeys, armor=true, asStream=message&&message.fromStream, detached=false, date=new Date(), fromUserId={} }) { export function sign({ message, privateKeys, armor=true, streaming=message&&message.fromStream, detached=false, date=new Date(), fromUserId={} }) {
checkCleartextOrMessage(message); checkCleartextOrMessage(message);
privateKeys = toArray(privateKeys); privateKeys = toArray(privateKeys);
if (asyncProxy) { // use web worker if available if (asyncProxy) { // use web worker if available
return asyncProxy.delegate('sign', { return asyncProxy.delegate('sign', {
message, privateKeys, armor, asStream, detached, date, fromUserId message, privateKeys, armor, streaming, detached, date, fromUserId
}); });
} }
@ -414,7 +419,7 @@ export function sign({ message, privateKeys, armor=true, asStream=message&&messa
result.message = message; result.message = message;
} }
} }
return convertStreams(result, asStream, armor ? ['signature', 'data'] : []); return convertStreams(result, streaming, armor ? ['signature', 'data'] : []);
}).catch(onError.bind(null, 'Error signing cleartext message')); }).catch(onError.bind(null, 'Error signing cleartext message'));
} }
@ -422,7 +427,7 @@ export function sign({ message, privateKeys, armor=true, asStream=message&&messa
* Verifies signatures of cleartext signed message * Verifies signatures of cleartext signed message
* @param {Key|Array<Key>} publicKeys array of publicKeys or single key, to verify signatures * @param {Key|Array<Key>} publicKeys array of publicKeys or single key, to verify signatures
* @param {CleartextMessage} message cleartext message object with signatures * @param {CleartextMessage} message cleartext message object with signatures
* @param {Boolean} asStream (optional) whether to return data as a ReadableStream. Defaults to true if message was created from a Stream. * @param {Boolean} streaming (optional) whether to return data as a ReadableStream. Defaults to true if message was created from a Stream.
* @param {Signature} signature (optional) detached signature for verification * @param {Signature} signature (optional) detached signature for verification
* @param {Date} date (optional) use the given date for verification instead of the current time * @param {Date} date (optional) use the given date for verification instead of the current time
* @returns {Promise<Object>} cleartext with status of verified signatures in the form of: * @returns {Promise<Object>} cleartext with status of verified signatures in the form of:
@ -430,19 +435,24 @@ export function sign({ message, privateKeys, armor=true, asStream=message&&messa
* @async * @async
* @static * @static
*/ */
export function verify({ message, publicKeys, asStream=message&&message.fromStream, signature=null, date=new Date() }) { export function verify({ message, publicKeys, streaming=message&&message.fromStream, signature=null, date=new Date() }) {
checkCleartextOrMessage(message); checkCleartextOrMessage(message);
publicKeys = toArray(publicKeys); publicKeys = toArray(publicKeys);
if (asyncProxy) { // use web worker if available if (asyncProxy) { // use web worker if available
return asyncProxy.delegate('verify', { message, publicKeys, asStream, signature, date }); return asyncProxy.delegate('verify', { message, publicKeys, streaming, signature, date });
} }
return Promise.resolve().then(async function() { return Promise.resolve().then(async function() {
const result = {}; const result = {};
result.signatures = signature ? await message.verifyDetached(signature, publicKeys, date) : await message.verify(publicKeys, date, asStream); result.signatures = signature ? await message.verifyDetached(signature, publicKeys, date, streaming) : await message.verify(publicKeys, date, streaming);
result.data = message instanceof CleartextMessage ? message.getText() : message.getLiteralData(); result.data = message instanceof CleartextMessage ? message.getText() : message.getLiteralData();
await postProcess(result, asStream, message); result.data = await convertStream(result.data, streaming);
if (streaming) {
linkStreams(result, message);
} else {
await prepareSignatures(result.signatures);
}
return result; return result;
}).catch(onError.bind(null, 'Error verifying cleartext signed message')); }).catch(onError.bind(null, 'Error verifying cleartext signed message'));
} }
@ -557,14 +567,14 @@ function toArray(param) {
/** /**
* Convert data to or from Stream * Convert data to or from Stream
* @param {Object} data the data to convert * @param {Object} data the data to convert
* @param {Boolean} asStream (optional) whether to return a ReadableStream * @param {Boolean} streaming (optional) whether to return a ReadableStream
* @returns {Object} the data in the respective format * @returns {Object} the data in the respective format
*/ */
async function convertStream(data, asStream) { async function convertStream(data, streaming) {
if (!asStream && util.isStream(data)) { if (!streaming && util.isStream(data)) {
return stream.readToEnd(data); return stream.readToEnd(data);
} }
if (asStream && !util.isStream(data)) { if (streaming && !util.isStream(data)) {
return new ReadableStream({ return new ReadableStream({
start(controller) { start(controller) {
controller.enqueue(data); controller.enqueue(data);
@ -578,17 +588,17 @@ async function convertStream(data, asStream) {
/** /**
* Convert object properties from Stream * Convert object properties from Stream
* @param {Object} obj the data to convert * @param {Object} obj the data to convert
* @param {Boolean} asStream (optional) whether to return ReadableStreams * @param {Boolean} streaming (optional) whether to return ReadableStreams
* @param {Boolean} keys (optional) which keys to return as streams, if possible * @param {Boolean} keys (optional) which keys to return as streams, if possible
* @returns {Object} the data in the respective format * @returns {Object} the data in the respective format
*/ */
async function convertStreams(obj, asStream, keys=[]) { async function convertStreams(obj, streaming, keys=[]) {
if (Object.prototype.isPrototypeOf(obj)) { if (Object.prototype.isPrototypeOf(obj)) {
await Promise.all(Object.entries(obj).map(async ([key, value]) => { // recursively search all children await Promise.all(Object.entries(obj).map(async ([key, value]) => { // recursively search all children
if (util.isStream(value) || keys.includes(key)) { if (util.isStream(value) || keys.includes(key)) {
obj[key] = await convertStream(value, asStream); obj[key] = await convertStream(value, streaming);
} else { } else {
await convertStreams(obj[key], asStream); await convertStreams(obj[key], streaming);
} }
})); }));
} }
@ -596,39 +606,38 @@ async function convertStreams(obj, asStream, keys=[]) {
} }
/** /**
* Post process the result of decrypt() and verify() before returning. * Link result.data to the message stream for cancellation.
* See comments in the function body for more details. * Also, forward errors in the message to result.data.
* @param {Object} result the data to convert * @param {Object} result the data to convert
* @param {Boolean} asStream whether to return ReadableStreams * @param {Message} message message object
* @param {Message} message message object * @param {ReadableStream} erroringStream (optional) stream which either errors or gets closed without data
* @param {ReadableStream} errorStream (optional) stream which either errors or gets closed without data * @returns {Object}
* @returns {Object} the data in the respective format
*/ */
async function postProcess(result, asStream, message, errorStream) { function linkStreams(result, message, erroringStream) {
// Convert result.data to a stream or Uint8Array depending on asStream result.data = stream.transformPair(message.packets.stream, async (readable, writable) => {
result.data = await convertStream(result.data, asStream); await stream.pipe(result.data, writable, {
if (asStream) { preventClose: true
// Link result.data to the message stream for cancellation
result.data = stream.transformPair(message.packets.stream, async (readable, writable) => {
await stream.pipe(result.data, writable, {
preventClose: true
});
const writer = stream.getWriter(writable);
try {
// Forward errors in errorStream (defaults to the message stream) to result.data
await stream.readToEnd(errorStream || readable, arr => arr);
await writer.close();
} catch(e) {
await writer.abort(e);
}
}); });
} else { const writer = stream.getWriter(writable);
// Convert signature promises to values try {
await Promise.all(result.signatures.map(async signature => { // Forward errors in erroringStream (defaulting to the message stream) to result.data.
signature.signature = await signature.signature; await stream.readToEnd(erroringStream || readable, arr => arr);
signature.valid = await signature.verified; await writer.close();
})); } catch(e) {
} await writer.abort(e);
}
});
}
/**
* Wait until signature objects have been verified
* @param {Object} signatures list of signatures
*/
async function prepareSignatures(signatures) {
await Promise.all(signatures.map(async signature => {
signature.signature = await signature.signature;
signature.valid = await signature.verified;
}));
} }

View File

@ -1802,11 +1802,11 @@ describe('OpenPGP.js public api tests', function() {
message: openpgp.message.fromBinary(data), message: openpgp.message.fromBinary(data),
privateKeys: privateKey.keys, privateKeys: privateKey.keys,
armor: false, armor: false,
asStream: true streaming: 'web'
}; };
const verifyOpt = { const verifyOpt = {
publicKeys: publicKey.keys, publicKeys: publicKey.keys,
asStream: true streaming: 'web'
}; };
return openpgp.sign(signOpt).then(function (signed) { return openpgp.sign(signOpt).then(function (signed) {
const packets = new openpgp.packet.List(); const packets = new openpgp.packet.List();
@ -1815,6 +1815,7 @@ describe('OpenPGP.js public api tests', function() {
verifyOpt.message = new openpgp.message.Message(packets); verifyOpt.message = new openpgp.message.Message(packets);
return openpgp.verify(verifyOpt); return openpgp.verify(verifyOpt);
}).then(async function (verified) { }).then(async function (verified) {
expect(openpgp.stream.isStream(verified.data)).to.equal('web');
expect([].slice.call(await openpgp.stream.readToEnd(verified.data))).to.deep.equal([].slice.call(data)); expect([].slice.call(await openpgp.stream.readToEnd(verified.data))).to.deep.equal([].slice.call(data));
expect(await verified.signatures[0].verified).to.be.true; expect(await verified.signatures[0].verified).to.be.true;
expect(await signOpt.privateKeys[0].getSigningKey(verified.signatures[0].keyid)) expect(await signOpt.privateKeys[0].getSigningKey(verified.signatures[0].keyid))