Don't return streams inside unarmored generated keys and signatures

When not requested, we convert the streams to Uint8Arrays.

This makes the generated key safe to pass to a Worker more than once.

Partially reverts 735aa1da.
This commit is contained in:
Daniel Huigens 2018-07-04 18:11:21 +02:00
parent 85223093a4
commit 1101a05b10
6 changed files with 66 additions and 49 deletions

View File

@ -127,14 +127,14 @@ export function generateKey({ userIds=[], passphrase="", numBits=2048, keyExpira
const revocationCertificate = key.getRevocationCertificate(); const revocationCertificate = key.getRevocationCertificate();
key.revocationSignatures = []; key.revocationSignatures = [];
return { return convertStreams({
key: key, key: key,
privateKeyArmored: await convertStream(key.armor()), privateKeyArmored: key.armor(),
publicKeyArmored: await convertStream(key.toPublic().armor()), publicKeyArmored: key.toPublic().armor(),
revocationCertificate: revocationCertificate revocationCertificate: revocationCertificate
}; });
}).catch(onError.bind(null, 'Error generating keypair')); }).catch(onError.bind(null, 'Error generating keypair'));
} }
@ -309,7 +309,7 @@ export function encrypt({ message, publicKeys, privateKeys, passwords, sessionKe
if (privateKeys.length || signature) { // sign the message only if private keys or signature is specified if (privateKeys.length || signature) { // sign the message only if private keys or signature is specified
if (detached) { if (detached) {
const detachedSignature = await message.signDetached(privateKeys, signature, date, fromUserId); const detachedSignature = await message.signDetached(privateKeys, signature, date, fromUserId);
result.signature = armor ? await convertStream(detachedSignature.armor(), asStream) : detachedSignature; result.signature = armor ? detachedSignature.armor() : detachedSignature;
} else { } else {
message = await message.sign(privateKeys, signature, date, fromUserId); message = await message.sign(privateKeys, signature, date, fromUserId);
} }
@ -320,14 +320,13 @@ export function encrypt({ message, publicKeys, privateKeys, passwords, sessionKe
}).then(async encrypted => { }).then(async encrypted => {
if (armor) { if (armor) {
result.data = encrypted.message.armor(); result.data = encrypted.message.armor();
result.data = await convertStream(result.data, asStream);
} else { } else {
result.message = encrypted.message; result.message = encrypted.message;
} }
if (returnSessionKey) { if (returnSessionKey) {
result.sessionKey = encrypted.sessionKey; result.sessionKey = encrypted.sessionKey;
} }
return result; return convertStreams(result, asStream, armor ? ['signature', 'data'] : []);
}).catch(onError.bind(null, 'Error encrypting message')); }).catch(onError.bind(null, 'Error encrypting message'));
} }
@ -425,17 +424,16 @@ export function sign({ message, privateKeys, armor=true, asStream=message&&messa
return Promise.resolve().then(async function() { return Promise.resolve().then(async function() {
if (detached) { if (detached) {
const signature = await message.signDetached(privateKeys, undefined, date, fromUserId); const signature = await message.signDetached(privateKeys, undefined, date, fromUserId);
result.signature = armor ? await convertStream(signature.armor(), asStream) : signature; result.signature = armor ? signature.armor() : signature;
} else { } else {
message = await message.sign(privateKeys, undefined, date, fromUserId); message = await message.sign(privateKeys, undefined, date, fromUserId);
if (armor) { if (armor) {
result.data = message.armor(); result.data = message.armor();
result.data = await convertStream(result.data, asStream);
} else { } else {
result.message = message; result.message = message;
} }
} }
return result; return convertStreams(result, asStream, armor ? ['signature', 'data'] : []);
}).catch(onError.bind(null, 'Error signing cleartext message')); }).catch(onError.bind(null, 'Error signing cleartext message'));
} }
@ -597,8 +595,8 @@ 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 whether to return a ReadableStream * @param {Boolean} asStream (optional) whether to return a ReadableStream
* @returns {Object} the parse data in the respective format * @returns {Object} the data in the respective format
*/ */
async function convertStream(data, asStream) { async function convertStream(data, asStream) {
if (!asStream && util.isStream(data)) { if (!asStream && util.isStream(data)) {
@ -615,6 +613,26 @@ async function convertStream(data, asStream) {
return data; return data;
} }
/**
* Convert object properties from Stream
* @param {Object} obj the data to convert
* @param {Boolean} asStream (optional) whether to return ReadableStreams
* @param {Boolean} keys (optional) which keys to return as streams, if possible
* @returns {Object} the data in the respective format
*/
async function convertStreams(obj, asStream, keys=[]) {
if (Object.prototype.isPrototypeOf(obj)) {
await Promise.all(Object.entries(obj).map(async ([key, value]) => { // recursively search all children
if (util.isStream(value) || keys.includes(key)) {
obj[key] = await convertStream(value, asStream);
} else {
await convertStreams(obj[key], asStream);
}
}));
}
return obj;
}
/** /**
* Global error handler that logs the stack trace and rethrows a high lvl error message. * Global error handler that logs the stack trace and rethrows a high lvl error message.

View File

@ -213,14 +213,14 @@ describe('Brainpool Cryptography', function () {
function omnibus() { function omnibus() {
it('Omnibus BrainpoolP256r1 Test', function () { it('Omnibus BrainpoolP256r1 Test', function () {
const options = { userIds: {name: "Hi", email: "hi@hel.lo"}, curve: "brainpoolP256r1" }; const options = { userIds: {name: "Hi", email: "hi@hel.lo"}, curve: "brainpoolP256r1" };
return openpgp.generateKey(options).then(async function (firstKey) { return openpgp.generateKey(options).then(function (firstKey) {
const hi = (await openpgp.key.readArmored(firstKey.privateKeyArmored)).keys[0]; const hi = firstKey.key;
const pubHi = (await openpgp.key.readArmored(firstKey.publicKeyArmored)).keys[0]; const pubHi = hi.toPublic();
const options = { userIds: { name: "Bye", email: "bye@good.bye" }, curve: "brainpoolP256r1" }; const options = { userIds: { name: "Bye", email: "bye@good.bye" }, curve: "brainpoolP256r1" };
return openpgp.generateKey(options).then(async function (secondKey) { return openpgp.generateKey(options).then(function (secondKey) {
const bye = (await openpgp.key.readArmored(secondKey.privateKeyArmored)).keys[0]; const bye = secondKey.key;
const pubBye = (await openpgp.key.readArmored(secondKey.publicKeyArmored)).keys[0]; const pubBye = bye.toPublic();
const testData = input.createSomeMessage(); const testData = input.createSomeMessage();
const testData2 = input.createSomeMessage(); const testData2 = input.createSomeMessage();

View File

@ -241,14 +241,14 @@ describe('Elliptic Curve Cryptography', function () {
const options = { userIds: {name: "Hi", email: "hi@hel.lo"}, curve: "p256" }; const options = { userIds: {name: "Hi", email: "hi@hel.lo"}, curve: "p256" };
const testData = input.createSomeMessage(); const testData = input.createSomeMessage();
const testData2 = input.createSomeMessage(); const testData2 = input.createSomeMessage();
return openpgp.generateKey(options).then(async function (firstKey) { return openpgp.generateKey(options).then(function (firstKey) {
const hi = (await openpgp.key.readArmored(firstKey.privateKeyArmored)).keys[0]; const hi = firstKey.key;
const pubHi = (await openpgp.key.readArmored(firstKey.publicKeyArmored)).keys[0]; const pubHi = hi.toPublic();
const options = { userIds: { name: "Bye", email: "bye@good.bye" }, curve: "brainpoolP256r1" }; const options = { userIds: { name: "Bye", email: "bye@good.bye" }, curve: "p256" };
return openpgp.generateKey(options).then(async function (secondKey) { return openpgp.generateKey(options).then(function (secondKey) {
const bye = (await openpgp.key.readArmored(secondKey.privateKeyArmored)).keys[0]; const bye = secondKey.key;
const pubBye = (await openpgp.key.readArmored(secondKey.publicKeyArmored)).keys[0]; const pubBye = bye.toPublic();
return Promise.all([ return Promise.all([
// Signing message // Signing message

View File

@ -1837,8 +1837,8 @@ VYGdb3eNlV8CfoEC
const opt = {numBits: 512, userIds: userId, passphrase: 'passphrase'}; const opt = {numBits: 512, userIds: userId, passphrase: 'passphrase'};
if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys
const key = (await openpgp.generateKey(opt)).key; const key = (await openpgp.generateKey(opt)).key;
const armor1 = await openpgp.stream.readToEnd(key.armor()); const armor1 = key.armor();
const armor2 = await openpgp.stream.readToEnd(key.armor()); const armor2 = key.armor();
expect(armor1).to.equal(armor2); expect(armor1).to.equal(armor2);
expect(await key.decrypt('passphrase')).to.be.true; expect(await key.decrypt('passphrase')).to.be.true;
expect(key.isDecrypted()).to.be.true; expect(key.isDecrypted()).to.be.true;
@ -1848,7 +1848,7 @@ VYGdb3eNlV8CfoEC
expect(key.isDecrypted()).to.be.false; expect(key.isDecrypted()).to.be.false;
expect(await key.decrypt('new_passphrase')).to.be.true; expect(await key.decrypt('new_passphrase')).to.be.true;
expect(key.isDecrypted()).to.be.true; expect(key.isDecrypted()).to.be.true;
const armor3 = await openpgp.stream.readToEnd(key.armor()); const armor3 = key.armor();
expect(armor3).to.not.equal(armor1); expect(armor3).to.not.equal(armor1);
}); });

View File

@ -1712,15 +1712,16 @@ describe('OpenPGP.js public api tests', function() {
message, message,
privateKeys: privateKey_1337.keys, privateKeys: privateKey_1337.keys,
detached: true, detached: true,
date: past date: past,
armor: false
}; };
const verifyOpt = { const verifyOpt = {
message, message,
publicKeys: publicKey_1337.keys, publicKeys: publicKey_1337.keys,
date: past date: past
}; };
return openpgp.sign(signOpt).then(async function (signed) { return openpgp.sign(signOpt).then(function (signed) {
verifyOpt.signature = await openpgp.signature.readArmored(signed.signature); verifyOpt.signature = signed.signature;
return openpgp.verify(verifyOpt).then(function (verified) { return openpgp.verify(verifyOpt).then(function (verified) {
expect(+verified.signatures[0].signature.packets[0].created).to.equal(+past); expect(+verified.signatures[0].signature.packets[0].created).to.equal(+past);
expect(verified.data).to.equal(openpgp.util.removeTrailingSpaces(plaintext)); expect(verified.data).to.equal(openpgp.util.removeTrailingSpaces(plaintext));

View File

@ -212,6 +212,7 @@ describe('X25519 Cryptography', function () {
expect(result.signatures[0].valid).to.be.true; expect(result.signatures[0].valid).to.be.true;
}); });
// TODO export, then reimport key and validate
function omnibus() { function omnibus() {
it('Omnibus Ed25519/Curve25519 Test', function () { it('Omnibus Ed25519/Curve25519 Test', function () {
const options = { const options = {
@ -222,14 +223,12 @@ describe('X25519 Cryptography', function () {
expect(firstKey).to.exist; expect(firstKey).to.exist;
expect(firstKey.privateKeyArmored).to.exist; expect(firstKey.privateKeyArmored).to.exist;
expect(firstKey.publicKeyArmored).to.exist; expect(firstKey.publicKeyArmored).to.exist;
expect(firstKey.key).to.exist;
expect(firstKey.key.primaryKey).to.exist;
expect(firstKey.key.subKeys).to.have.length(1);
expect(firstKey.key.subKeys[0].keyPacket).to.exist;
const hi = (await openpgp.key.readArmored(firstKey.privateKeyArmored)).keys[0]; const hi = firstKey.key;
const pubHi = (await openpgp.key.readArmored(firstKey.publicKeyArmored)).keys[0];
expect(hi).to.exist;
expect(hi.primaryKey).to.exist;
expect(hi.subKeys).to.have.length(1);
expect(hi.subKeys[0].subKey).to.exist;
const primaryKey = hi.primaryKey; const primaryKey = hi.primaryKey;
const subKey = hi.subKeys[0]; const subKey = hi.subKeys[0];
expect(hi.getAlgorithmInfo().curve).to.equal('ed25519'); expect(hi.getAlgorithmInfo().curve).to.equal('ed25519');
@ -243,7 +242,7 @@ describe('X25519 Cryptography', function () {
primaryKey, { userId: user.userId, key: primaryKey } primaryKey, { userId: user.userId, key: primaryKey }
)).to.eventually.be.true; )).to.eventually.be.true;
await expect(user.verifyCertificate( await expect(user.verifyCertificate(
primaryKey, user.selfCertifications[0], [pubHi] primaryKey, user.selfCertifications[0], [hi.toPublic()]
)).to.eventually.equal(openpgp.enums.keyStatus.valid); )).to.eventually.equal(openpgp.enums.keyStatus.valid);
const options = { const options = {
@ -251,8 +250,7 @@ describe('X25519 Cryptography', function () {
curve: "curve25519" curve: "curve25519"
}; };
return openpgp.generateKey(options).then(async function (secondKey) { return openpgp.generateKey(options).then(async function (secondKey) {
const bye = (await openpgp.key.readArmored(secondKey.privateKeyArmored)).keys[0]; const bye = secondKey.key;
const pubBye = (await openpgp.key.readArmored(secondKey.publicKeyArmored)).keys[0];
expect(bye.getAlgorithmInfo().curve).to.equal('ed25519'); expect(bye.getAlgorithmInfo().curve).to.equal('ed25519');
expect(bye.getAlgorithmInfo().algorithm).to.equal('eddsa'); expect(bye.getAlgorithmInfo().algorithm).to.equal('eddsa');
expect(bye.subKeys[0].getAlgorithmInfo().curve).to.equal('curve25519'); expect(bye.subKeys[0].getAlgorithmInfo().curve).to.equal('curve25519');
@ -264,14 +262,14 @@ describe('X25519 Cryptography', function () {
bye.primaryKey, { userId: user.userId, key: bye.primaryKey } bye.primaryKey, { userId: user.userId, key: bye.primaryKey }
)).to.eventually.be.true; )).to.eventually.be.true;
await expect(user.verifyCertificate( await expect(user.verifyCertificate(
bye.primaryKey, user.selfCertifications[0], [pubBye] bye.primaryKey, user.selfCertifications[0], [bye.toPublic()]
)).to.eventually.equal(openpgp.enums.keyStatus.valid); )).to.eventually.equal(openpgp.enums.keyStatus.valid);
return Promise.all([ return Promise.all([
// Hi trusts Bye! // Hi trusts Bye!
pubBye.signPrimaryUser([hi]).then(trustedBye => { bye.toPublic().signPrimaryUser([hi]).then(trustedBye => {
expect(trustedBye.users[0].otherCertifications[0].verify( expect(trustedBye.users[0].otherCertifications[0].verify(
primaryKey, { userId: user.userId, key: pubBye.primaryKey } primaryKey, { userId: user.userId, key: bye.toPublic().primaryKey }
)).to.eventually.be.true; )).to.eventually.be.true;
}), }),
// Signing message // Signing message
@ -282,12 +280,12 @@ describe('X25519 Cryptography', function () {
// Verifying signed message // Verifying signed message
return Promise.all([ return Promise.all([
openpgp.verify( openpgp.verify(
{ message: msg, publicKeys: pubHi } { message: msg, publicKeys: hi.toPublic() }
).then(output => expect(output.signatures[0].valid).to.be.true), ).then(output => expect(output.signatures[0].valid).to.be.true),
// Verifying detached signature // Verifying detached signature
openpgp.verify( openpgp.verify(
{ message: openpgp.message.fromText('Hi, this is me, Hi!'), { message: openpgp.message.fromText('Hi, this is me, Hi!'),
publicKeys: pubHi, publicKeys: hi.toPublic(),
signature: await openpgp.signature.readArmored(signed.data) } signature: await openpgp.signature.readArmored(signed.data) }
).then(output => expect(output.signatures[0].valid).to.be.true) ).then(output => expect(output.signatures[0].valid).to.be.true)
]); ]);
@ -295,7 +293,7 @@ describe('X25519 Cryptography', function () {
// Encrypting and signing // Encrypting and signing
openpgp.encrypt( openpgp.encrypt(
{ message: openpgp.message.fromText('Hi, Hi wrote this but only Bye can read it!'), { message: openpgp.message.fromText('Hi, Hi wrote this but only Bye can read it!'),
publicKeys: [pubBye], publicKeys: [bye.toPublic()],
privateKeys: [hi] } privateKeys: [hi] }
).then(async encrypted => { ).then(async encrypted => {
const msg = await openpgp.message.readArmored(encrypted.data); const msg = await openpgp.message.readArmored(encrypted.data);
@ -303,7 +301,7 @@ describe('X25519 Cryptography', function () {
return openpgp.decrypt( return openpgp.decrypt(
{ message: msg, { message: msg,
privateKeys: bye, privateKeys: bye,
publicKeys: [pubHi] } publicKeys: [hi.toPublic()] }
).then(output => { ).then(output => {
expect(output.data).to.equal('Hi, Hi wrote this but only Bye can read it!'); expect(output.data).to.equal('Hi, Hi wrote this but only Bye can read it!');
expect(output.signatures[0].valid).to.be.true; expect(output.signatures[0].valid).to.be.true;