From 79160abcc9fcb9253a8770c66389fea8b7b03c13 Mon Sep 17 00:00:00 2001 From: Sanjana Rajan Date: Wed, 22 Mar 2017 17:04:21 -0700 Subject: [PATCH 1/6] allow signature as input in high level encrypt --- src/message.js | 41 ++++++++++++++++++++++++++++++++--------- src/openpgp.js | 14 +++++++++----- 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/src/message.js b/src/message.js index 8580fd36..b51ef574 100644 --- a/src/message.js +++ b/src/message.js @@ -104,12 +104,12 @@ Message.prototype.decrypt = function(privateKey, sessionKey, password) { enums.packet.symEncryptedIntegrityProtected, enums.packet.symEncryptedAEADProtected ); - if (symEncryptedPacketlist.length === 0) { return; } const symEncryptedPacket = symEncryptedPacketlist[0]; + return symEncryptedPacket.decrypt(keyObj.algorithm, keyObj.data).then(() => { const resultMsg = new Message(symEncryptedPacket.packets); symEncryptedPacket.packets = new packet.List(); // remove packets after decryption @@ -292,10 +292,11 @@ export function encryptSessionKey(sessionKey, symAlgo, publicKeys, passwords) { /** * Sign the message (the literal data packet of the message) - * @param {Array} privateKey private keys with decrypted secret key data for signing - * @return {module:message~Message} new message with signed content + * @param {Array} privateKey private keys with decrypted secret key data for signing + * @param {Signature} signature (optional) any existing detached signature to add to the message + * @return {module:message~Message} new message with signed content */ -Message.prototype.sign = function(privateKeys) { +Message.prototype.sign = function(privateKeys=[], signature=null) { var packetlist = new packet.List(); @@ -307,12 +308,27 @@ Message.prototype.sign = function(privateKeys) { var literalFormat = enums.write(enums.literal, literalDataPacket.format); var signatureType = literalFormat === enums.literal.binary ? enums.signature.binary : enums.signature.text; - var i, signingKeyPacket; + var i, signingKeyPacket, existingSigPacketlist, onePassSig; + + if (signature) { + existingSigPacketlist = signature.packets.filterByTag(enums.packet.signature); + if (existingSigPacketlist.length) { + for (i = existingSigPacketlist.length - 1; i >= 0; i--) { + var sigPacket = existingSigPacketlist[i]; + onePassSig = new packet.OnePassSignature(); + onePassSig.type = signatureType; + onePassSig.hashAlgorithm = config.prefer_hash_algorithm; + onePassSig.publicKeyAlgorithm = sigPacket.publicKeyAlgorithm; + onePassSig.signingKeyId = sigPacket.issuerKeyId; + packetlist.push(onePassSig); + } + } + } for (i = 0; i < privateKeys.length; i++) { if (privateKeys[i].isPublic()) { throw new Error('Need private key for signing'); } - var onePassSig = new packet.OnePassSignature(); + onePassSig = new packet.OnePassSignature(); onePassSig.type = signatureType; //TODO get preferred hashg algo from key signature onePassSig.hashAlgorithm = config.prefer_hash_algorithm; @@ -341,16 +357,19 @@ Message.prototype.sign = function(privateKeys) { signaturePacket.sign(signingKeyPacket, literalDataPacket); packetlist.push(signaturePacket); } - + if (signature) { + packetlist.concat(existingSigPacketlist); + } return new Message(packetlist); }; /** * Create a detached signature for the message (the literal data packet of the message) - * @param {Array} privateKey private keys with decrypted secret key data for signing + * @param {Array} privateKey private keys with decrypted secret key data for signing + * @param {Signature} signature (optional) any existing detached signature * @return {module:signature~Signature} new detached signature of message content */ -Message.prototype.signDetached = function(privateKeys) { +Message.prototype.signDetached = function(privateKeys=[], signature=null) { var packetlist = new packet.List(); @@ -375,6 +394,10 @@ Message.prototype.signDetached = function(privateKeys) { signaturePacket.sign(signingKeyPacket, literalDataPacket); packetlist.push(signaturePacket); } + if (signature) { + var existingSigPacketlist = signature.packets.filterByTag(enums.packet.signature); + packetlist.concat(existingSigPacketlist); + } return new sigModule.Signature(packetlist); }; diff --git a/src/openpgp.js b/src/openpgp.js index e3ad9404..d98773ed 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -180,12 +180,13 @@ export function decryptKey({ privateKey, passphrase }) { * @param {String} filename (optional) a filename for the literal data packet * @param {Boolean} armor (optional) if the return values should be ascii armored or the message/signature objects * @param {Boolean} detached (optional) if the signature should be detached (if true, signature will be added to returned object) + * @param {Signature} signatureInput (optional) a detached signature to add to the encrypted message * @return {Promise} encrypted (and optionally signed message) in the form: * {data: ASCII armored message if 'armor' is true, * message: full Message object if 'armor' is false, signature: detached signature if 'detached' is true} * @static */ -export function encrypt({ data, publicKeys, privateKeys, passwords, filename, armor=true, detached=false }) { +export function encrypt({ data, publicKeys, privateKeys, passwords, filename, armor=true, detached=false, signatureInput=null }) { checkData(data); publicKeys = toArray(publicKeys); privateKeys = toArray(privateKeys); passwords = toArray(passwords); if (!nativeAEAD() && asyncProxy) { // use web worker if web crypto apis are not supported @@ -195,16 +196,19 @@ export function encrypt({ data, publicKeys, privateKeys, passwords, filename, ar return Promise.resolve().then(() => { let message = createMessage(data, filename); - if (privateKeys) { // sign the message only if private keys are specified + if (!privateKeys) { + privateKeys = []; + } + if (privateKeys.length || signatureInput) { // sign the message only if private keys or signatureInput is specified if (detached) { - var signature = message.signDetached(privateKeys); + var signature = message.signDetached(privateKeys, signatureInput); if (armor) { result.signature = signature.armor(); } else { result.signature = signature; } } else { - message = message.sign(privateKeys); + message = message.sign(privateKeys, signatureInput); } } return message.encrypt(publicKeys, passwords); @@ -243,6 +247,7 @@ export function decrypt({ message, privateKey, publicKeys, sessionKey, password, return message.decrypt(privateKey, sessionKey, password).then(message => { const result = parseMessage(message, format); + if (result.data) { // verify if (!publicKeys) { publicKeys = []; @@ -281,7 +286,6 @@ export function decrypt({ message, privateKey, publicKeys, sessionKey, password, export function sign({ data, privateKeys, armor=true, detached=false}) { checkString(data); privateKeys = toArray(privateKeys); - if (asyncProxy) { // use web worker if available return asyncProxy.delegate('sign', { data, privateKeys, armor, detached }); } From 25d16046c2f5fa3c28965efb113ca79c2e60bb0c Mon Sep 17 00:00:00 2001 From: Sanjana Rajan Date: Wed, 22 Mar 2017 17:14:30 -0700 Subject: [PATCH 2/6] add one pass in case where no priv keys are passed in for signing --- src/message.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/message.js b/src/message.js index b51ef574..4c4705c1 100644 --- a/src/message.js +++ b/src/message.js @@ -320,6 +320,9 @@ Message.prototype.sign = function(privateKeys=[], signature=null) { onePassSig.hashAlgorithm = config.prefer_hash_algorithm; onePassSig.publicKeyAlgorithm = sigPacket.publicKeyAlgorithm; onePassSig.signingKeyId = sigPacket.issuerKeyId; + if (!privateKeys.length && i === 0) { + onePassSig.flags = 1; + } packetlist.push(onePassSig); } } From 0f967331c6dae78898fd85bcefe27e31f31b2363 Mon Sep 17 00:00:00 2001 From: Sanjana Rajan Date: Wed, 22 Mar 2017 17:14:57 -0700 Subject: [PATCH 3/6] upper level tests --- test/general/openpgp.js | 136 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/test/general/openpgp.js b/test/general/openpgp.js index ac3fd50d..daf969bf 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -689,6 +689,142 @@ describe('OpenPGP.js public api tests', function() { }); }); + it('should encrypt and decrypt/verify with detached signature input and detached flag set for encryption', function(done) { + var signOpt = { + data: plaintext, + privateKeys: privateKey.keys[0], + detached: true + }; + + var encOpt = { + data: plaintext, + publicKeys: publicKey.keys, + detached: true + }; + + var decOpt = { + privateKey: privateKey.keys[0], + publicKeys: publicKey.keys[0] + }; + + openpgp.sign(signOpt).then(function(signed) { + encOpt.signatureInput = openpgp.signature.readArmored(signed.signature); + return openpgp.encrypt(encOpt); + }).then(function(encrypted) { + decOpt.message = openpgp.message.readArmored(encrypted.data); + decOpt.signature = openpgp.signature.readArmored(encrypted.signature); + return openpgp.decrypt(decOpt); + }).then(function(decrypted) { + expect(decrypted.data).to.equal(plaintext); + expect(decrypted.signatures[0].valid).to.be.true; + expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); + expect(decrypted.signatures[0].signature.packets.length).to.equal(1); + done(); + }); + }); + + it('should encrypt and decrypt/verify with detached signature as input and detached flag not set for encryption', function(done) { + var signOpt = { + data: plaintext, + privateKeys: privateKey.keys[0], + detached: true + }; + + var encOpt = { + data: plaintext, + publicKeys: publicKey.keys, + privateKeys: privateKey.keys[0] + }; + + var decOpt = { + privateKey: privateKey.keys[0], + publicKeys: publicKey.keys + }; + + openpgp.sign(signOpt).then(function(signed) { + encOpt.signatureInput = openpgp.signature.readArmored(signed.signature); + return openpgp.encrypt(encOpt); + }).then(function(encrypted) { + decOpt.message = openpgp.message.readArmored(encrypted.data); + return openpgp.decrypt(decOpt); + }).then(function(decrypted) { + expect(decrypted.data).to.equal(plaintext); + expect(decrypted.signatures[0].valid).to.be.true; + expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); + expect(decrypted.signatures[0].signature.packets.length).to.equal(1); + expect(decrypted.signatures[1].valid).to.be.true; + expect(decrypted.signatures[1].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); + expect(decrypted.signatures[1].signature.packets.length).to.equal(1); + done(); + }); + }); + + it('should fail to encrypt and decrypt/verify with detached signature input and detached flag set for encryption with wrong public key', function(done) { + var signOpt = { + data: plaintext, + privateKeys: privateKey.keys, + detached: true + }; + + var encOpt = { + data: plaintext, + publicKeys: publicKey.keys, + detached: true + }; + + var decOpt = { + privateKey: privateKey.keys[0], + publicKeys: openpgp.key.readArmored(wrong_pubkey).keys + }; + + openpgp.sign(signOpt).then(function(signed) { + encOpt.signatureInput = openpgp.signature.readArmored(signed.signature); + return openpgp.encrypt(encOpt); + }).then(function(encrypted) { + decOpt.message = openpgp.message.readArmored(encrypted.data); + decOpt.signature = openpgp.signature.readArmored(encrypted.signature); + return openpgp.decrypt(decOpt); + }).then(function(decrypted) { + expect(decrypted.data).to.equal(plaintext); + expect(decrypted.signatures[0].valid).to.be.null; + expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); + expect(decrypted.signatures[0].signature.packets.length).to.equal(1); + done(); + }); + }); + + it('should fail to encrypt and decrypt/verify with detached signature as input and detached flag not set for encryption with wrong public key', function(done) { + var signOpt = { + data: plaintext, + privateKeys: privateKey.keys, + detached: true + }; + + var encOpt = { + data: plaintext, + publicKeys: publicKey.keys + }; + + var decOpt = { + privateKey: privateKey.keys[0], + publicKeys: openpgp.key.readArmored(wrong_pubkey).keys + }; + + openpgp.sign(signOpt).then(function(signed) { + encOpt.signatureInput = openpgp.signature.readArmored(signed.signature); + return openpgp.encrypt(encOpt); + }).then(function(encrypted) { + decOpt.message = openpgp.message.readArmored(encrypted.data); + return openpgp.decrypt(decOpt); + }).then(function(decrypted) { + expect(decrypted.data).to.equal(plaintext); + expect(decrypted.signatures[0].valid).to.be.null; + expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); + expect(decrypted.signatures[0].signature.packets.length).to.equal(1); + done(); + }); + }); + it('should fail to verify decrypted data with wrong public pgp key', function(done) { var encOpt = { data: plaintext, From d04c09687ddb51c401ba108756377c54945369f8 Mon Sep 17 00:00:00 2001 From: Sanjana Rajan Date: Thu, 23 Mar 2017 11:16:16 -0700 Subject: [PATCH 4/6] spacing --- src/message.js | 4 +++- src/openpgp.js | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/message.js b/src/message.js index 4c4705c1..c415e195 100644 --- a/src/message.js +++ b/src/message.js @@ -104,12 +104,12 @@ Message.prototype.decrypt = function(privateKey, sessionKey, password) { enums.packet.symEncryptedIntegrityProtected, enums.packet.symEncryptedAEADProtected ); + if (symEncryptedPacketlist.length === 0) { return; } const symEncryptedPacket = symEncryptedPacketlist[0]; - return symEncryptedPacket.decrypt(keyObj.algorithm, keyObj.data).then(() => { const resultMsg = new Message(symEncryptedPacket.packets); symEncryptedPacket.packets = new packet.List(); // remove packets after decryption @@ -360,9 +360,11 @@ Message.prototype.sign = function(privateKeys=[], signature=null) { signaturePacket.sign(signingKeyPacket, literalDataPacket); packetlist.push(signaturePacket); } + if (signature) { packetlist.concat(existingSigPacketlist); } + return new Message(packetlist); }; diff --git a/src/openpgp.js b/src/openpgp.js index d98773ed..216056da 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -247,7 +247,6 @@ export function decrypt({ message, privateKey, publicKeys, sessionKey, password, return message.decrypt(privateKey, sessionKey, password).then(message => { const result = parseMessage(message, format); - if (result.data) { // verify if (!publicKeys) { publicKeys = []; @@ -286,6 +285,7 @@ export function decrypt({ message, privateKey, publicKeys, sessionKey, password, export function sign({ data, privateKeys, armor=true, detached=false}) { checkString(data); privateKeys = toArray(privateKeys); + if (asyncProxy) { // use web worker if available return asyncProxy.delegate('sign', { data, privateKeys, armor, detached }); } From 484d5aa1abb316781c279349b24930ad1a4202ce Mon Sep 17 00:00:00 2001 From: Sanjana Rajan Date: Thu, 23 Mar 2017 12:04:32 -0700 Subject: [PATCH 5/6] improve test --- test/general/openpgp.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/test/general/openpgp.js b/test/general/openpgp.js index daf969bf..cfaed9d8 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -724,9 +724,14 @@ describe('OpenPGP.js public api tests', function() { }); it('should encrypt and decrypt/verify with detached signature as input and detached flag not set for encryption', function(done) { + var privKeyDE = openpgp.key.readArmored(priv_key_de).keys[0]; + privKeyDE.decrypt(passphrase); + + var pubKeyDE = openpgp.key.readArmored(pub_key_de).keys[0]; + var signOpt = { data: plaintext, - privateKeys: privateKey.keys[0], + privateKeys: privKeyDE, detached: true }; @@ -738,7 +743,7 @@ describe('OpenPGP.js public api tests', function() { var decOpt = { privateKey: privateKey.keys[0], - publicKeys: publicKey.keys + publicKeys: [publicKey.keys[0], pubKeyDE] }; openpgp.sign(signOpt).then(function(signed) { @@ -753,7 +758,7 @@ describe('OpenPGP.js public api tests', function() { expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); expect(decrypted.signatures[0].signature.packets.length).to.equal(1); expect(decrypted.signatures[1].valid).to.be.true; - expect(decrypted.signatures[1].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); + expect(decrypted.signatures[1].keyid.toHex()).to.equal(privKeyDE.getSigningKeyPacket().getKeyId().toHex()); expect(decrypted.signatures[1].signature.packets.length).to.equal(1); done(); }); From 16c6d2f1625fc3097d2305851ff44438d8dfa16e Mon Sep 17 00:00:00 2001 From: Sanjana Rajan Date: Thu, 23 Mar 2017 13:01:12 -0700 Subject: [PATCH 6/6] signatureInput -> signature --- src/openpgp.js | 16 ++++++++-------- test/general/openpgp.js | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/openpgp.js b/src/openpgp.js index 216056da..c2181519 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -180,17 +180,17 @@ export function decryptKey({ privateKey, passphrase }) { * @param {String} filename (optional) a filename for the literal data packet * @param {Boolean} armor (optional) if the return values should be ascii armored or the message/signature objects * @param {Boolean} detached (optional) if the signature should be detached (if true, signature will be added to returned object) - * @param {Signature} signatureInput (optional) a detached signature to add to the encrypted message + * @param {Signature} signature (optional) a detached signature to add to the encrypted message * @return {Promise} encrypted (and optionally signed message) in the form: * {data: ASCII armored message if 'armor' is true, * message: full Message object if 'armor' is false, signature: detached signature if 'detached' is true} * @static */ -export function encrypt({ data, publicKeys, privateKeys, passwords, filename, armor=true, detached=false, signatureInput=null }) { +export function encrypt({ data, publicKeys, privateKeys, passwords, filename, armor=true, detached=false, signature=null }) { checkData(data); publicKeys = toArray(publicKeys); privateKeys = toArray(privateKeys); passwords = toArray(passwords); if (!nativeAEAD() && asyncProxy) { // use web worker if web crypto apis are not supported - return asyncProxy.delegate('encrypt', { data, publicKeys, privateKeys, passwords, filename, armor, detached }); + return asyncProxy.delegate('encrypt', { data, publicKeys, privateKeys, passwords, filename, armor, detached, signature }); } var result = {}; return Promise.resolve().then(() => { @@ -199,16 +199,16 @@ export function encrypt({ data, publicKeys, privateKeys, passwords, filename, ar if (!privateKeys) { privateKeys = []; } - if (privateKeys.length || signatureInput) { // sign the message only if private keys or signatureInput is specified + if (privateKeys.length || signature) { // sign the message only if private keys or signature is specified if (detached) { - var signature = message.signDetached(privateKeys, signatureInput); + var detachedSignature = message.signDetached(privateKeys, signature); if (armor) { - result.signature = signature.armor(); + result.signature = detachedSignature.armor(); } else { - result.signature = signature; + result.signature = detachedSignature; } } else { - message = message.sign(privateKeys, signatureInput); + message = message.sign(privateKeys, signature); } } return message.encrypt(publicKeys, passwords); diff --git a/test/general/openpgp.js b/test/general/openpgp.js index cfaed9d8..01295fe6 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -708,7 +708,7 @@ describe('OpenPGP.js public api tests', function() { }; openpgp.sign(signOpt).then(function(signed) { - encOpt.signatureInput = openpgp.signature.readArmored(signed.signature); + encOpt.signature = openpgp.signature.readArmored(signed.signature); return openpgp.encrypt(encOpt); }).then(function(encrypted) { decOpt.message = openpgp.message.readArmored(encrypted.data); @@ -747,7 +747,7 @@ describe('OpenPGP.js public api tests', function() { }; openpgp.sign(signOpt).then(function(signed) { - encOpt.signatureInput = openpgp.signature.readArmored(signed.signature); + encOpt.signature = openpgp.signature.readArmored(signed.signature); return openpgp.encrypt(encOpt); }).then(function(encrypted) { decOpt.message = openpgp.message.readArmored(encrypted.data); @@ -783,7 +783,7 @@ describe('OpenPGP.js public api tests', function() { }; openpgp.sign(signOpt).then(function(signed) { - encOpt.signatureInput = openpgp.signature.readArmored(signed.signature); + encOpt.signature = openpgp.signature.readArmored(signed.signature); return openpgp.encrypt(encOpt); }).then(function(encrypted) { decOpt.message = openpgp.message.readArmored(encrypted.data); @@ -816,7 +816,7 @@ describe('OpenPGP.js public api tests', function() { }; openpgp.sign(signOpt).then(function(signed) { - encOpt.signatureInput = openpgp.signature.readArmored(signed.signature); + encOpt.signature = openpgp.signature.readArmored(signed.signature); return openpgp.encrypt(encOpt); }).then(function(encrypted) { decOpt.message = openpgp.message.readArmored(encrypted.data);