Don't keep extra copies of streams in memory
This commit is contained in:
parent
78a0ca937e
commit
56ec5b3a8d
|
@ -45,7 +45,7 @@ function KeyPair(curve, options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyPair.prototype.sign = async function (message, hash_algo, hashed) {
|
KeyPair.prototype.sign = async function (message, hash_algo, hashed) {
|
||||||
if (!message.locked) {
|
if (message && !message.locked) {
|
||||||
message = await stream.readToEnd(message);
|
message = await stream.readToEnd(message);
|
||||||
if (this.curve.web && util.getWebCrypto()) {
|
if (this.curve.web && util.getWebCrypto()) {
|
||||||
// If browser doesn't support a curve, we'll catch it
|
// If browser doesn't support a curve, we'll catch it
|
||||||
|
@ -65,7 +65,7 @@ KeyPair.prototype.sign = async function (message, hash_algo, hashed) {
|
||||||
};
|
};
|
||||||
|
|
||||||
KeyPair.prototype.verify = async function (message, signature, hash_algo, hashed) {
|
KeyPair.prototype.verify = async function (message, signature, hash_algo, hashed) {
|
||||||
if (!message.locked) {
|
if (message && !message.locked) {
|
||||||
message = await stream.readToEnd(message);
|
message = await stream.readToEnd(message);
|
||||||
if (this.curve.web && util.getWebCrypto()) {
|
if (this.curve.web && util.getWebCrypto()) {
|
||||||
// If browser doesn't support a curve, we'll catch it
|
// If browser doesn't support a curve, we'll catch it
|
||||||
|
|
|
@ -127,6 +127,9 @@ Message.prototype.decrypt = async function(privateKeys, passwords, sessionKeys)
|
||||||
exception = e;
|
exception = e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// We don't await stream.cancel here because... it sometimes hangs indefinitely. No clue why.
|
||||||
|
stream.cancel(symEncryptedPacket.encrypted); // Don't keep copy of encrypted data in memory.
|
||||||
|
symEncryptedPacket.encrypted = null;
|
||||||
|
|
||||||
if (!symEncryptedPacket.packets || !symEncryptedPacket.packets.length) {
|
if (!symEncryptedPacket.packets || !symEncryptedPacket.packets.length) {
|
||||||
throw exception || new Error('Decryption failed.');
|
throw exception || new Error('Decryption failed.');
|
||||||
|
@ -163,6 +166,8 @@ Message.prototype.decryptSessionKeys = async function(privateKeys, passwords) {
|
||||||
util.print_debug_error(err);
|
util.print_debug_error(err);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
stream.cancel(keyPacket.encrypted); // Don't keep copy of encrypted data in memory.
|
||||||
|
keyPacket.encrypted = null;
|
||||||
}));
|
}));
|
||||||
} else if (privateKeys) {
|
} else if (privateKeys) {
|
||||||
const pkESKeyPacketlist = this.packets.filterByTag(enums.packet.publicKeyEncryptedSessionKey);
|
const pkESKeyPacketlist = this.packets.filterByTag(enums.packet.publicKeyEncryptedSessionKey);
|
||||||
|
@ -188,6 +193,8 @@ Message.prototype.decryptSessionKeys = async function(privateKeys, passwords) {
|
||||||
util.print_debug_error(err);
|
util.print_debug_error(err);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
stream.cancel(keyPacket.encrypted); // Don't keep copy of encrypted data in memory.
|
||||||
|
keyPacket.encrypted = null;
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
throw new Error('No key or password specified.');
|
throw new Error('No key or password specified.');
|
||||||
|
@ -543,7 +550,7 @@ Message.prototype.verify = async function(keys, date=new Date()) {
|
||||||
onePassSig.signatureData = stream.fromAsync(() => new Promise(resolve => {
|
onePassSig.signatureData = stream.fromAsync(() => new Promise(resolve => {
|
||||||
onePassSig.signatureDataResolve = resolve;
|
onePassSig.signatureDataResolve = resolve;
|
||||||
}));
|
}));
|
||||||
onePassSig.hash(literalDataList[0]);
|
onePassSig.hashed = onePassSig.hash(literalDataList[0]);
|
||||||
});
|
});
|
||||||
const reader = stream.getReader(msg.packets.stream);
|
const reader = stream.getReader(msg.packets.stream);
|
||||||
for (let i = 0; ; i++) {
|
for (let i = 0; ; i++) {
|
||||||
|
|
|
@ -365,14 +365,15 @@ export function decrypt({ message, privateKeys, passwords, sessionKeys, publicKe
|
||||||
}
|
}
|
||||||
|
|
||||||
return message.decrypt(privateKeys, passwords, sessionKeys).then(async function(message) {
|
return message.decrypt(privateKeys, passwords, sessionKeys).then(async function(message) {
|
||||||
|
|
||||||
const result = await parseMessage(message, format, asStream);
|
|
||||||
|
|
||||||
if (!publicKeys) {
|
if (!publicKeys) {
|
||||||
publicKeys = [];
|
publicKeys = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const result = {};
|
||||||
result.signatures = signature ? await message.verifyDetached(signature, publicKeys, date) : await message.verify(publicKeys, date);
|
result.signatures = signature ? await message.verifyDetached(signature, publicKeys, date) : await message.verify(publicKeys, date);
|
||||||
|
result.data = format === 'binary' ? message.getLiteralData() : message.getText();
|
||||||
|
result.data = await convertStream(result.data, asStream);
|
||||||
|
result.filename = message.getFilename();
|
||||||
return result;
|
return result;
|
||||||
}).catch(onError.bind(null, 'Error decrypting message'));
|
}).catch(onError.bind(null, 'Error decrypting message'));
|
||||||
}
|
}
|
||||||
|
@ -596,27 +597,6 @@ function createMessage(data, filename, date=new Date(), type) {
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse the message given a certain format.
|
|
||||||
* @param {Message} message the message object to be parse
|
|
||||||
* @param {String} format the output format e.g. 'utf8' or 'binary'
|
|
||||||
* @param {Boolean} asStream whether to return a ReadableStream
|
|
||||||
* @returns {Object} the parse data in the respective format
|
|
||||||
*/
|
|
||||||
async function parseMessage(message, format, asStream) {
|
|
||||||
let data;
|
|
||||||
if (format === 'binary') {
|
|
||||||
data = message.getLiteralData();
|
|
||||||
} else if (format === 'utf8') {
|
|
||||||
data = message.getText();
|
|
||||||
} else {
|
|
||||||
throw new Error('Invalid format');
|
|
||||||
}
|
|
||||||
data = await convertStream(data, asStream);
|
|
||||||
const filename = message.getFilename();
|
|
||||||
return { data, filename };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
|
|
|
@ -61,8 +61,8 @@ Literal.prototype.setText = function(text, format='utf8') {
|
||||||
* with normalized end of line to \n
|
* with normalized end of line to \n
|
||||||
* @returns {String} literal data as text
|
* @returns {String} literal data as text
|
||||||
*/
|
*/
|
||||||
Literal.prototype.getText = function() {
|
Literal.prototype.getText = function(clone=false) {
|
||||||
if (this.text === null) {
|
if (this.text === null || this.text.locked) {
|
||||||
let lastChar = '';
|
let lastChar = '';
|
||||||
const decoder = new TextDecoder('utf8');
|
const decoder = new TextDecoder('utf8');
|
||||||
// eslint-disable-next-line no-inner-declarations
|
// eslint-disable-next-line no-inner-declarations
|
||||||
|
@ -79,9 +79,9 @@ Literal.prototype.getText = function() {
|
||||||
lastChar = '';
|
lastChar = '';
|
||||||
return normalized;
|
return normalized;
|
||||||
}
|
}
|
||||||
this.text = stream.transform(stream.clone(this.data), process, () => process(new Uint8Array(), true));
|
this.text = stream.transform(this.getBytes(clone), process, () => process(new Uint8Array(), true));
|
||||||
}
|
}
|
||||||
return stream.clone(this.text);
|
return this.text;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -100,14 +100,17 @@ Literal.prototype.setBytes = function(bytes, format) {
|
||||||
* Get the byte sequence representing the literal packet data
|
* Get the byte sequence representing the literal packet data
|
||||||
* @returns {Uint8Array} A sequence of bytes
|
* @returns {Uint8Array} A sequence of bytes
|
||||||
*/
|
*/
|
||||||
Literal.prototype.getBytes = function() {
|
Literal.prototype.getBytes = function(clone=false) {
|
||||||
if (this.data === null) {
|
if (this.data === null) {
|
||||||
// normalize EOL to \r\n
|
// normalize EOL to \r\n
|
||||||
const text = util.canonicalizeEOL(this.text);
|
const text = util.canonicalizeEOL(this.text);
|
||||||
// encode UTF8
|
// encode UTF8
|
||||||
this.data = util.str_to_Uint8Array(util.encode_utf8(text));
|
this.data = util.str_to_Uint8Array(util.encode_utf8(text));
|
||||||
}
|
}
|
||||||
return stream.clone(this.data);
|
if (clone) {
|
||||||
|
return stream.clone(this.data);
|
||||||
|
}
|
||||||
|
return this.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -561,12 +561,12 @@ Signature.prototype.toSign = function (type, data) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case t.binary:
|
case t.binary:
|
||||||
if (data.text !== null) {
|
if (data.text !== null) {
|
||||||
return util.str_to_Uint8Array(data.getText());
|
return util.str_to_Uint8Array(data.getText(true));
|
||||||
}
|
}
|
||||||
return data.getBytes();
|
return data.getBytes(true);
|
||||||
|
|
||||||
case t.text: {
|
case t.text: {
|
||||||
let text = data.getText();
|
let text = data.getText(true);
|
||||||
// normalize EOL to \r\n
|
// normalize EOL to \r\n
|
||||||
text = util.canonicalizeEOL(text);
|
text = util.canonicalizeEOL(text);
|
||||||
// encode UTF8
|
// encode UTF8
|
||||||
|
@ -659,11 +659,8 @@ Signature.prototype.toHash = function(data) {
|
||||||
};
|
};
|
||||||
|
|
||||||
Signature.prototype.hash = function(data, toHash) {
|
Signature.prototype.hash = function(data, toHash) {
|
||||||
if (!this.hashed) {
|
const hashAlgorithm = enums.write(enums.hash, this.hashAlgorithm);
|
||||||
const hashAlgorithm = enums.write(enums.hash, this.hashAlgorithm);
|
return crypto.hash.digest(hashAlgorithm, toHash || this.toHash(data));
|
||||||
this.hashed = crypto.hash.digest(hashAlgorithm, toHash || this.toHash(data));
|
|
||||||
}
|
|
||||||
return this.hashed;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -679,8 +676,15 @@ Signature.prototype.verify = async function (key, data) {
|
||||||
const publicKeyAlgorithm = enums.write(enums.publicKey, this.publicKeyAlgorithm);
|
const publicKeyAlgorithm = enums.write(enums.publicKey, this.publicKeyAlgorithm);
|
||||||
const hashAlgorithm = enums.write(enums.hash, this.hashAlgorithm);
|
const hashAlgorithm = enums.write(enums.hash, this.hashAlgorithm);
|
||||||
|
|
||||||
const toHash = this.toHash(data);
|
let toHash;
|
||||||
const hash = await stream.readToEnd(this.hash(data, toHash));
|
let hash;
|
||||||
|
if (this.hashed) {
|
||||||
|
hash = this.hashed;
|
||||||
|
} else {
|
||||||
|
toHash = this.toHash(data);
|
||||||
|
hash = this.hash(data, toHash);
|
||||||
|
}
|
||||||
|
hash = await stream.readToEnd(hash);
|
||||||
|
|
||||||
if (this.signedHashValue[0] !== hash[0] ||
|
if (this.signedHashValue[0] !== hash[0] ||
|
||||||
this.signedHashValue[1] !== hash[1]) {
|
this.signedHashValue[1] !== hash[1]) {
|
||||||
|
|
|
@ -96,7 +96,7 @@ SymEncryptedAEADProtected.prototype.decrypt = async function (sessionKeyAlgorith
|
||||||
if (config.aead_protect_version !== 4) {
|
if (config.aead_protect_version !== 4) {
|
||||||
this.cipherAlgo = enums.write(enums.symmetric, sessionKeyAlgorithm);
|
this.cipherAlgo = enums.write(enums.symmetric, sessionKeyAlgorithm);
|
||||||
}
|
}
|
||||||
await this.packets.read(await this.crypt('decrypt', key, this.encrypted));
|
await this.packets.read(await this.crypt('decrypt', key, stream.clone(this.encrypted)));
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -62,8 +62,16 @@ function tee(input) {
|
||||||
function clone(input) {
|
function clone(input) {
|
||||||
if (util.isStream(input)) {
|
if (util.isStream(input)) {
|
||||||
const teed = tee(input);
|
const teed = tee(input);
|
||||||
input.getReader = teed[0].getReader.bind(teed[0]);
|
// Overwrite input.getReader, input.locked, etc to point to teed[0]
|
||||||
input.tee = teed[0].tee.bind(teed[0]);
|
Object.entries(Object.getOwnPropertyDescriptors(ReadableStream.prototype)).forEach(([name, descriptor]) => {
|
||||||
|
if (name === 'constructor') return;
|
||||||
|
if (descriptor.value) {
|
||||||
|
descriptor.value = descriptor.value.bind(teed[0]);
|
||||||
|
} else {
|
||||||
|
descriptor.get = descriptor.get.bind(teed[0]);
|
||||||
|
}
|
||||||
|
Object.defineProperty(input, name, descriptor);
|
||||||
|
});
|
||||||
return teed[1];
|
return teed[1];
|
||||||
}
|
}
|
||||||
return subarray(input);
|
return subarray(input);
|
||||||
|
@ -108,6 +116,12 @@ async function readToEnd(input, join) {
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function cancel(input) {
|
||||||
|
if (util.isStream(input)) {
|
||||||
|
return input.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function fromAsync(fn) {
|
function fromAsync(fn) {
|
||||||
return new ReadableStream({
|
return new ReadableStream({
|
||||||
pull: async controller => {
|
pull: async controller => {
|
||||||
|
@ -187,7 +201,7 @@ if (nodeStream) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default { concat, getReader, transform, clone, subarray, readToEnd, nodeToWeb, webToNode, fromAsync };
|
export default { concat, getReader, transform, clone, subarray, readToEnd, cancel, nodeToWeb, webToNode, fromAsync };
|
||||||
|
|
||||||
|
|
||||||
/*const readerAcquiredMap = new Map();
|
/*const readerAcquiredMap = new Map();
|
||||||
|
|
|
@ -1828,14 +1828,13 @@ describe('OpenPGP.js public api tests', function() {
|
||||||
const literals = packets.packets.filterByTag(openpgp.enums.packet.literal);
|
const literals = packets.packets.filterByTag(openpgp.enums.packet.literal);
|
||||||
expect(literals.length).to.equal(1);
|
expect(literals.length).to.equal(1);
|
||||||
expect(+literals[0].date).to.equal(+past);
|
expect(+literals[0].date).to.equal(+past);
|
||||||
expect(await openpgp.stream.readToEnd(packets.getText())).to.equal(plaintext);
|
const signatures = await packets.verify(encryptOpt.publicKeys, past);
|
||||||
return packets.verify(encryptOpt.publicKeys, past);
|
|
||||||
}).then(function (signatures) {
|
|
||||||
expect(+signatures[0].signature.packets[0].created).to.equal(+past);
|
expect(+signatures[0].signature.packets[0].created).to.equal(+past);
|
||||||
expect(signatures[0].valid).to.be.true;
|
expect(signatures[0].valid).to.be.true;
|
||||||
expect(encryptOpt.privateKeys[0].getSigningKey(signatures[0].keyid, past))
|
expect(encryptOpt.privateKeys[0].getSigningKey(signatures[0].keyid, past))
|
||||||
.to.be.not.null;
|
.to.be.not.null;
|
||||||
expect(signatures[0].signature.packets.length).to.equal(1);
|
expect(signatures[0].signature.packets.length).to.equal(1);
|
||||||
|
expect(await openpgp.stream.readToEnd(packets.getText())).to.equal(plaintext);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1857,14 +1856,13 @@ describe('OpenPGP.js public api tests', function() {
|
||||||
expect(literals.length).to.equal(1);
|
expect(literals.length).to.equal(1);
|
||||||
expect(literals[0].format).to.equal('binary');
|
expect(literals[0].format).to.equal('binary');
|
||||||
expect(+literals[0].date).to.equal(+future);
|
expect(+literals[0].date).to.equal(+future);
|
||||||
expect(await openpgp.stream.readToEnd(packets.getLiteralData())).to.deep.equal(data);
|
const signatures = await packets.verify(encryptOpt.publicKeys, future);
|
||||||
return packets.verify(encryptOpt.publicKeys, future);
|
|
||||||
}).then(function (signatures) {
|
|
||||||
expect(+signatures[0].signature.packets[0].created).to.equal(+future);
|
expect(+signatures[0].signature.packets[0].created).to.equal(+future);
|
||||||
expect(signatures[0].valid).to.be.true;
|
expect(signatures[0].valid).to.be.true;
|
||||||
expect(encryptOpt.privateKeys[0].getSigningKey(signatures[0].keyid, future))
|
expect(encryptOpt.privateKeys[0].getSigningKey(signatures[0].keyid, future))
|
||||||
.to.be.not.null;
|
.to.be.not.null;
|
||||||
expect(signatures[0].signature.packets.length).to.equal(1);
|
expect(signatures[0].signature.packets.length).to.equal(1);
|
||||||
|
expect(await openpgp.stream.readToEnd(packets.getLiteralData())).to.deep.equal(data);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1887,14 +1885,13 @@ describe('OpenPGP.js public api tests', function() {
|
||||||
expect(literals.length).to.equal(1);
|
expect(literals.length).to.equal(1);
|
||||||
expect(literals[0].format).to.equal('mime');
|
expect(literals[0].format).to.equal('mime');
|
||||||
expect(+literals[0].date).to.equal(+future);
|
expect(+literals[0].date).to.equal(+future);
|
||||||
expect(await openpgp.stream.readToEnd(packets.getLiteralData())).to.deep.equal(data);
|
const signatures = await packets.verify(encryptOpt.publicKeys, future);
|
||||||
return packets.verify(encryptOpt.publicKeys, future);
|
|
||||||
}).then(function (signatures) {
|
|
||||||
expect(+signatures[0].signature.packets[0].created).to.equal(+future);
|
expect(+signatures[0].signature.packets[0].created).to.equal(+future);
|
||||||
expect(signatures[0].valid).to.be.true;
|
expect(signatures[0].valid).to.be.true;
|
||||||
expect(encryptOpt.privateKeys[0].getSigningKey(signatures[0].keyid, future))
|
expect(encryptOpt.privateKeys[0].getSigningKey(signatures[0].keyid, future))
|
||||||
.to.be.not.null;
|
.to.be.not.null;
|
||||||
expect(signatures[0].signature.packets.length).to.equal(1);
|
expect(signatures[0].signature.packets.length).to.equal(1);
|
||||||
|
expect(await openpgp.stream.readToEnd(packets.getLiteralData())).to.deep.equal(data);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -523,15 +523,15 @@ describe("Signature", function() {
|
||||||
expect(pubKey2.getKeys(keyids[1])).to.not.be.empty;
|
expect(pubKey2.getKeys(keyids[1])).to.not.be.empty;
|
||||||
expect(pubKey3.getKeys(keyids[0])).to.not.be.empty;
|
expect(pubKey3.getKeys(keyids[0])).to.not.be.empty;
|
||||||
|
|
||||||
expect(await openpgp.stream.readToEnd(sMsg.getText())).to.equal(plaintext);
|
return sMsg.verify([pubKey2, pubKey3]).then(async verifiedSig => {
|
||||||
|
|
||||||
return sMsg.verify([pubKey2, pubKey3]).then(verifiedSig => {
|
|
||||||
expect(verifiedSig).to.exist;
|
expect(verifiedSig).to.exist;
|
||||||
expect(verifiedSig).to.have.length(2);
|
expect(verifiedSig).to.have.length(2);
|
||||||
expect(verifiedSig[0].valid).to.be.true;
|
expect(verifiedSig[0].valid).to.be.true;
|
||||||
expect(verifiedSig[1].valid).to.be.true;
|
expect(verifiedSig[1].valid).to.be.true;
|
||||||
expect(verifiedSig[0].signature.packets.length).to.equal(1);
|
expect(verifiedSig[0].signature.packets.length).to.equal(1);
|
||||||
expect(verifiedSig[1].signature.packets.length).to.equal(1);
|
expect(verifiedSig[1].signature.packets.length).to.equal(1);
|
||||||
|
|
||||||
|
expect(await openpgp.stream.readToEnd(sMsg.getText())).to.equal(plaintext);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user