From 73a240df6cff7c921edd68577a65742ad1a5b9a0 Mon Sep 17 00:00:00 2001 From: Mahrud Sayrafi Date: Mon, 5 Mar 2018 19:51:30 -0800 Subject: [PATCH] Simplifies (Key|User|SubKey).isRevoked, API changes in key.js For User s/revocationCertifications/revocationSignatures/g For Key/SubKey s/revocationSignature/revocationSignatures/g is now an array. --- src/cleartext.js | 3 +- src/key.js | 20 +++++++--- src/keyring/keyring.js | 2 +- src/message.js | 13 ++++--- src/openpgp.js | 10 +++-- src/packet/secret_key.js | 2 +- src/packet/sym_encrypted_session_key.js | 4 +- src/type/keyid.js | 1 + test/general/ecc_nist.js | 1 - test/general/key.js | 41 ++++++++++---------- test/general/packet.js | 50 ++++++++++++------------- test/general/signature.js | 10 +++-- test/general/x25519.js | 2 - 13 files changed, 85 insertions(+), 74 deletions(-) diff --git a/src/cleartext.js b/src/cleartext.js index 36bf6f6b..a794319a 100644 --- a/src/cleartext.js +++ b/src/cleartext.js @@ -155,8 +155,7 @@ export function readArmored(armoredText) { packetlist.read(input.data); verifyHeaders(input.headers, packetlist); const signature = new Signature(packetlist); - const newMessage = new CleartextMessage(input.text, signature); - return newMessage; + return new CleartextMessage(input.text, signature); } /** diff --git a/src/key.js b/src/key.js index 4b045d33..a54e61ed 100644 --- a/src/key.js +++ b/src/key.js @@ -529,9 +529,10 @@ Key.prototype.getPrimaryUser = function(date=new Date()) { * Update key with new components from specified key with same key ID: * users, subkeys, certificates are merged into the destination key, * duplicates and expired signatures are ignored. + * * If the specified key is a private key and the destination key is public, * the destination key is transformed to a private key. - * @param {module:key~Key} key source key to merge + * @param {module:key~Key} key Source key to merge */ Key.prototype.update = async function(key) { const that = this; @@ -903,8 +904,9 @@ User.prototype.verify = async function(primaryKey) { /** * Update user with new components from specified user - * @param {module:key~User} user source user to merge - * @param {module:packet/signature} primaryKey primary key used for validation + * @param {module:key~User} user Source user to merge + * @param {module:packet/secret_key| + module:packet/secret_subkey} primaryKey primary key used for validation */ User.prototype.update = async function(user, primaryKey) { const dataToVerify = { userid: this.userId || this.userAttribute, key: primaryKey }; @@ -1037,8 +1039,9 @@ SubKey.prototype.getExpirationTime = function() { /** * Update subkey with new components from specified subkey - * @param {module:key~SubKey} subKey source subkey to merge - * @param {module:packet/signature} primaryKey primary key used for validation + * @param {module:key~SubKey} subKey Source subkey to merge + * @param {module:packet/secret_key| + module:packet/secret_subkey} primaryKey primary key used for validation */ SubKey.prototype.update = async function(subKey, primaryKey) { if (await subKey.verify(primaryKey) === enums.keyStatus.invalid) { @@ -1226,7 +1229,12 @@ export async function reformat(options) { throw new Error('Only RSA Encrypt or Sign supported'); } - if (!options.privateKey.decrypt()) { + try { + const isDecrypted = options.privateKey.getKeyPackets().every(keyPacket => keyPacket.isDecrypted); + if (!isDecrypted) { + await options.privateKey.decrypt(); + } + } catch (err) { throw new Error('Key not decrypted'); } diff --git a/src/keyring/keyring.js b/src/keyring/keyring.js index 32d09738..4bf39e4e 100644 --- a/src/keyring/keyring.js +++ b/src/keyring/keyring.js @@ -161,7 +161,7 @@ KeyArray.prototype.getForId = function (keyId, deep) { if (keyIdCheck(keyId, this.keys[i].primaryKey)) { return this.keys[i]; } - if (deep && this.keys[i].subKeys) { + if (deep && this.keys[i].subKeys.length) { for (let j = 0; j < this.keys[i].subKeys.length; j++) { if (keyIdCheck(keyId, this.keys[i].subKeys[j].subKey)) { return this.keys[i]; diff --git a/src/message.js b/src/message.js index af6344b0..6293859f 100644 --- a/src/message.js +++ b/src/message.js @@ -138,7 +138,8 @@ Message.prototype.decrypt = async function(privateKeys, passwords, sessionKeys) * Decrypt encrypted session keys either with private keys or passwords. * @param {Array} privateKeys (optional) private keys with decrypted secret data * @param {Array} passwords (optional) passwords used to decrypt - * @returns {Promise{Array<{ data:Uint8Array, algorithm:String }>}} array of object with potential sessionKey, algorithm pairs + * @returns {Promise>} array of object with potential sessionKey, algorithm pairs */ Message.prototype.decryptSessionKeys = async function(privateKeys, passwords) { let keyPackets = []; @@ -162,6 +163,7 @@ Message.prototype.decryptSessionKeys = async function(privateKeys, passwords) { throw new Error('No public key encrypted session key packet found.'); } await Promise.all(pkESKeyPacketlist.map(async function(keyPacket) { + // TODO improve this const privateKeyPackets = privateKeys.reduce(function(acc, privateKey) { return acc.concat(privateKey.getKeyPackets(keyPacket.publicKeyId)); }, new packet.List()); @@ -534,11 +536,12 @@ Message.prototype.verifyDetached = function(signature, keys, date=new Date()) { /** * Create list of objects containing signer's keyid and validity of signature - * @param {Array} signatureList array of signature packets - * @param {Array} literalDataList array of literal data packets - * @param {Array} keys array of keys to verify signatures + * @param {Array} signatureList array of signature packets + * @param {Array} literalDataList array of literal data packets + * @param {Array} keys array of keys to verify signatures * @param {Date} date Verify the signature against the given date, i.e. check signature creation time < date < expiration time - * @returns {Promise{Array<({keyid: module:type/keyid, valid: Boolean})>}} list of signer's keyid and validity of signature + * @returns {Promise>} list of signer's keyid and validity of signature */ export async function createVerificationObjects(signatureList, literalDataList, keys, date=new Date()) { return Promise.all(signatureList.map(async function(signature) { diff --git a/src/openpgp.js b/src/openpgp.js index 4f73229a..201b4d64 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -371,8 +371,10 @@ export function verify({ message, publicKeys, signature=null, date=new Date() }) return Promise.resolve().then(async function() { const result = {}; - result.data = CleartextMessage.prototype.isPrototypeOf(message) ? message.getText() : message.getLiteralData(); - result.signatures = signature ? await message.verifyDetached(signature, publicKeys, date) : await message.verify(publicKeys, date); + result.data = message instanceof CleartextMessage ? message.getText() : message.getLiteralData(); + result.signatures = signature ? + await message.verifyDetached(signature, publicKeys, date) : + await message.verify(publicKeys, date); return result; }).catch(onError.bind(null, 'Error verifying cleartext signed message')); } @@ -462,12 +464,12 @@ function checkData(data, name) { } } function checkMessage(message) { - if (!messageLib.Message.prototype.isPrototypeOf(message)) { + if (!(message instanceof messageLib.Message)) { throw new Error('Parameter [message] needs to be of type Message'); } } function checkCleartextOrMessage(message) { - if (!CleartextMessage.prototype.isPrototypeOf(message) && !messageLib.Message.prototype.isPrototypeOf(message)) { + if (!(message instanceof CleartextMessage) && !(message instanceof messageLib.Message)) { throw new Error('Parameter [message] needs to be of type Message or CleartextMessage'); } } diff --git a/src/packet/secret_key.js b/src/packet/secret_key.js index 13fd624d..f23343fe 100644 --- a/src/packet/secret_key.js +++ b/src/packet/secret_key.js @@ -216,7 +216,7 @@ function produceEncryptionKey(s2k, passphrase, algorithm) { */ SecretKey.prototype.decrypt = async function (passphrase) { if (this.isDecrypted) { - return true; + throw new Error('Key packet is already decrypted.'); } let i = 0; diff --git a/src/packet/sym_encrypted_session_key.js b/src/packet/sym_encrypted_session_key.js index 3884f072..6433b0ab 100644 --- a/src/packet/sym_encrypted_session_key.js +++ b/src/packet/sym_encrypted_session_key.js @@ -104,7 +104,7 @@ SymEncryptedSessionKey.prototype.write = function() { /** * Decrypts the session key * @param {String} passphrase The passphrase in string form - * @return {Promise} */ SymEncryptedSessionKey.prototype.decrypt = async function(passphrase) { const algo = this.sessionKeyEncryptionAlgorithm !== null ? @@ -128,7 +128,7 @@ SymEncryptedSessionKey.prototype.decrypt = async function(passphrase) { /** * Encrypts the session key * @param {String} passphrase The passphrase in string form - * @return {Promise} */ SymEncryptedSessionKey.prototype.encrypt = async function(passphrase) { const algo = this.sessionKeyEncryptionAlgorithm !== null ? diff --git a/src/type/keyid.js b/src/type/keyid.js index c741bd51..16fd3324 100644 --- a/src/type/keyid.js +++ b/src/type/keyid.js @@ -53,6 +53,7 @@ Keyid.prototype.toHex = function() { }; Keyid.prototype.equals = function(keyid) { + // Note: checks if keyid is a wildcard, but doesn't check "this". return keyid.isWildcard() || this.bytes === keyid.bytes; }; diff --git a/test/general/ecc_nist.js b/test/general/ecc_nist.js index f88708d8..264e07cd 100644 --- a/test/general/ecc_nist.js +++ b/test/general/ecc_nist.js @@ -202,7 +202,6 @@ describe('Elliptic Curve Cryptography', function () { it('Encrypt and sign message', async function () { const romeoPrivate = await load_priv_key('romeo'); const julietPublic = load_pub_key('juliet'); - expect(await romeoPrivate.decrypt(data.romeo.pass)).to.be.true; const encrypted = await openpgp.encrypt({publicKeys: [julietPublic], privateKeys: [romeoPrivate], data: data.romeo.message + "\n"}); const message = openpgp.message.readArmored(encrypted.data); diff --git a/test/general/key.js b/test/general/key.js index a241e64f..dff1d7ed 100644 --- a/test/general/key.js +++ b/test/general/key.js @@ -769,7 +769,7 @@ describe('Key', function() { const pubKey = pubKeys.keys[0]; // remove subkeys - pubKey.subKeys = null; + pubKey.subKeys = []; // primary key has only key flags for signing const keyPacket = pubKey.getEncryptionKeyPacket(); expect(keyPacket).to.not.exist; @@ -798,13 +798,13 @@ describe('Key', function() { )()).to.be.rejectedWith('Key update method: fingerprints of keys not equal').notify(done); }); - it('update() - merge revocation signature', function(done) { + it('update() - merge revocation signatures', function(done) { const source = openpgp.key.readArmored(pub_revoked).keys[0]; const dest = openpgp.key.readArmored(pub_revoked).keys[0]; - expect(source.revocationSignature).to.exist; - dest.revocationSignature = null; + expect(source.revocationSignatures).to.exist; + dest.revocationSignatures = []; dest.update(source).then(() => { - expect(dest.revocationSignature).to.exist.and.be.an.instanceof(openpgp.packet.Signature); + expect(dest.revocationSignatures[0]).to.exist.and.be.an.instanceof(openpgp.packet.Signature); done(); }); }); @@ -821,18 +821,18 @@ describe('Key', function() { }); }); - it('update() - merge user - other and revocation certification', function(done) { + it('update() - merge user - other and certification revocation signatures', function(done) { const source = openpgp.key.readArmored(pub_sig_test).keys[0]; const dest = openpgp.key.readArmored(pub_sig_test).keys[0]; expect(source.users[1].otherCertifications).to.exist; - expect(source.users[1].revocationCertifications).to.exist; - dest.users[1].otherCertifications = null; - dest.users[1].revocationCertifications.pop(); + expect(source.users[1].revocationSignatures).to.exist; + dest.users[1].otherCertifications = []; + dest.users[1].revocationSignatures.pop(); dest.update(source).then(() => { expect(dest.users[1].otherCertifications).to.exist.and.to.have.length(1); expect(dest.users[1].otherCertifications[0].signature).to.equal(source.users[1].otherCertifications[0].signature); - expect(dest.users[1].revocationCertifications).to.exist.and.to.have.length(2); - expect(dest.users[1].revocationCertifications[1].signature).to.equal(source.users[1].revocationCertifications[1].signature); + expect(dest.users[1].revocationSignatures).to.exist.and.to.have.length(2); + expect(dest.users[1].revocationSignatures[1].signature).to.equal(source.users[1].revocationSignatures[1].signature); done(); }); }); @@ -851,15 +851,14 @@ describe('Key', function() { }); }); - it('update() - merge subkey - revocation signature', function(done) { + it('update() - merge subkey - revocation signature', function() { const source = openpgp.key.readArmored(pub_sig_test).keys[0]; const dest = openpgp.key.readArmored(pub_sig_test).keys[0]; - expect(source.subKeys[0].revocationSignature).to.exist; - dest.subKeys[0].revocationSignature = null; - dest.update(source).then(() => { - expect(dest.subKeys[0].revocationSignature).to.exist; - expect(dest.subKeys[0].revocationSignature.signature).to.equal(dest.subKeys[0].revocationSignature.signature); - done(); + expect(source.subKeys[0].revocationSignatures).to.exist; + dest.subKeys[0].revocationSignatures = []; + return dest.update(source).then(() => { + expect(dest.subKeys[0].revocationSignatures).to.exist; + expect(dest.subKeys[0].revocationSignatures[0].signature).to.equal(dest.subKeys[0].revocationSignatures[0].signature); }); }); @@ -886,8 +885,8 @@ describe('Key', function() { it('update() - merge private key into public key - no subkeys', function() { const source = openpgp.key.readArmored(priv_key_rsa).keys[0]; const dest = openpgp.key.readArmored(twoKeys).keys[0]; - source.subKeys = null; - dest.subKeys = null; + source.subKeys = []; + dest.subKeys = []; expect(dest.isPublic()).to.be.true; return dest.update(source).then(() => { expect(dest.isPrivate()).to.be.true; @@ -905,7 +904,7 @@ describe('Key', function() { it('update() - merge private key into public key - mismatch throws error', function(done) { const source = openpgp.key.readArmored(priv_key_rsa).keys[0]; const dest = openpgp.key.readArmored(twoKeys).keys[0]; - source.subKeys = null; + source.subKeys = []; expect(dest.subKeys).to.exist; expect(dest.isPublic()).to.be.true; expect(dest.update.bind(dest, source)()) diff --git a/test/general/packet.js b/test/general/packet.js index c5fe98d4..1c07bd76 100644 --- a/test/general/packet.js +++ b/test/general/packet.js @@ -141,7 +141,7 @@ describe("Packet", function() { }); }); - it('Sym encrypted session key with a compressed packet', function(done) { + it('Sym encrypted session key with a compressed packet', async function() { const msg = '-----BEGIN PGP MESSAGE-----\n' + 'Version: GnuPG v2.0.19 (GNU/Linux)\n' + @@ -156,16 +156,16 @@ describe("Packet", function() { const parsed = new openpgp.packet.List(); parsed.read(msgbytes); - parsed[0].decrypt('test'); + return parsed[0].decrypt('test').then(() => { + const key = parsed[0].sessionKey; + return parsed[1].decrypt(parsed[0].sessionKeyAlgorithm, key).then(() => { + const compressed = parsed[1].packets[0]; - const key = parsed[0].sessionKey; - parsed[1].decrypt(parsed[0].sessionKeyAlgorithm, key); - const compressed = parsed[1].packets[0]; + const result = stringify(compressed.packets[0].data); - const result = stringify(compressed.packets[0].data); - - expect(result).to.equal('Hello world!\n'); - done(); + expect(result).to.equal('Hello world!\n'); + }); + }); }); it('Public key encrypted symmetric key packet', function() { @@ -187,13 +187,13 @@ describe("Packet", function() { enc.publicKeyAlgorithm = 'rsa_encrypt'; enc.sessionKeyAlgorithm = 'aes256'; enc.publicKeyId.bytes = '12345678'; - enc.encrypt({ params: mpi }).then(() => { + return enc.encrypt({ params: mpi }).then(() => { msg.push(enc); msg2.read(msg.write()); - msg2[0].decrypt({ params: mpi }).then(() => { + return msg2[0].decrypt({ params: mpi }).then(() => { expect(stringify(msg2[0].sessionKey)).to.equal(stringify(enc.sessionKey)); expect(msg2[0].sessionKeyAlgorithm).to.equal(enc.sessionKeyAlgorithm); @@ -299,8 +299,8 @@ describe("Packet", function() { const msg = new openpgp.packet.List(); msg.read(openpgp.armor.decode(armored_msg).data); - return msg[0].decrypt(key).then(() => { - return msg[1].decrypt(msg[0].sessionKeyAlgorithm, msg[0].sessionKey); + return msg[0].decrypt(key).then(async () => { + await msg[1].decrypt(msg[0].sessionKeyAlgorithm, msg[0].sessionKey); const text = stringify(msg[1].packets[0].packets[0].data); @@ -339,7 +339,7 @@ describe("Packet", function() { expect(stringify(msg2[1].packets[0].data)).to.equal(stringify(literal.data)); }); - it('Secret key encryption/decryption test', function() { + it('Secret key encryption/decryption test', async function() { const armored_msg = '-----BEGIN PGP MESSAGE-----\n' + 'Version: GnuPG v2.0.19 (GNU/Linux)\n' + @@ -355,13 +355,13 @@ describe("Packet", function() { let key = new openpgp.packet.List(); key.read(openpgp.armor.decode(armored_key).data); key = key[3]; - key.decrypt('test'); + await key.decrypt('test'); const msg = new openpgp.packet.List(); msg.read(openpgp.armor.decode(armored_msg).data); - return msg[0].decrypt(key).then(() => { - return msg[1].decrypt(msg[0].sessionKeyAlgorithm, msg[0].sessionKey); + return msg[0].decrypt(key).then(async () => { + await msg[1].decrypt(msg[0].sessionKeyAlgorithm, msg[0].sessionKey); const text = stringify(msg[1].packets[0].packets[0].data); @@ -386,7 +386,7 @@ describe("Packet", function() { ]); }); - it('Reading a signed, encrypted message.', function(done) { + it('Reading a signed, encrypted message.', async function() { const armored_msg = '-----BEGIN PGP MESSAGE-----\n' + 'Version: GnuPG v2.0.19 (GNU/Linux)\n' + @@ -405,19 +405,19 @@ describe("Packet", function() { const key = new openpgp.packet.List(); key.read(openpgp.armor.decode(armored_key).data); - key[3].decrypt('test'); + await key[3].decrypt('test'); const msg = new openpgp.packet.List(); msg.read(openpgp.armor.decode(armored_msg).data); - msg[0].decrypt(key[3]).then(() => { - msg[1].decrypt(msg[0].sessionKeyAlgorithm, msg[0].sessionKey); + return msg[0].decrypt(key[3]).then(async () => { + await msg[1].decrypt(msg[0].sessionKeyAlgorithm, msg[0].sessionKey); const payload = msg[1].packets[0].packets; expect(payload[2].verify( key[0], payload[1] - )).to.eventually.be.true.notify(done); + )).to.eventually.be.true; }); }); @@ -428,7 +428,7 @@ describe("Packet", function() { const rsa = openpgp.crypto.publicKey.rsa; const keySize = openpgp.util.getWebCryptoAll() ? 2048 : 512; // webkit webcrypto accepts minimum 2048 bit keys - return rsa.generate(keySize, "10001").then(function(mpiGen) { + return rsa.generate(keySize, "10001").then(async function(mpiGen) { let mpi = [mpiGen.n, mpiGen.e, mpiGen.d, mpiGen.p, mpiGen.q, mpiGen.u]; mpi = mpi.map(function(k) { return new openpgp.MPI(k); @@ -436,13 +436,13 @@ describe("Packet", function() { key[0].params = mpi; key[0].algorithm = "rsa_sign"; - key[0].encrypt('hello'); + await key[0].encrypt('hello'); const raw = key.write(); const key2 = new openpgp.packet.List(); key2.read(raw); - key2[0].decrypt('hello'); + await key2[0].decrypt('hello'); expect(key[0].params.toString()).to.equal(key2[0].params.toString()); }); diff --git a/test/general/signature.js b/test/general/signature.js index 166cae47..0728e126 100644 --- a/test/general/signature.js +++ b/test/general/signature.js @@ -688,16 +688,18 @@ describe("Signature", function() { }); - it('Verify primary key revocation signature', function(done) { + // TODO add test with multiple revocation signatures + it('Verify primary key revocation signatures', function(done) { const pubKey = openpgp.key.readArmored(pub_revoked).keys[0]; - expect(pubKey.revocationSignature.verify( + expect(pubKey.revocationSignatures[0].verify( pubKey.primaryKey, {key: pubKey.primaryKey} )).to.eventually.be.true.notify(done); }); - it('Verify subkey revocation signature', function(done) { + // TODO add test with multiple revocation signatures + it('Verify subkey revocation signatures', function(done) { const pubKey = openpgp.key.readArmored(pub_revoked).keys[0]; - expect(pubKey.subKeys[0].revocationSignature.verify( + expect(pubKey.subKeys[0].revocationSignatures[0].verify( pubKey.primaryKey, {key: pubKey.primaryKey, bind: pubKey.subKeys[0].subKey} )).to.eventually.be.true.notify(done); }); diff --git a/test/general/x25519.js b/test/general/x25519.js index b7849304..1f1e62ae 100644 --- a/test/general/x25519.js +++ b/test/general/x25519.js @@ -184,7 +184,6 @@ describe('X25519 Cryptography', function () { it('Decrypt and verify message', async function () { const light = load_pub_key('light'); const night = await load_priv_key('night'); - expect(await night.decrypt(data.night.pass)).to.be.true; const msg = openpgp.message.readArmored(data.night.message_encrypted); const result = await openpgp.decrypt({ privateKeys: night, publicKeys: [light], message: msg }); @@ -198,7 +197,6 @@ describe('X25519 Cryptography', function () { it('Encrypt and sign message', async function () { const nightPublic = load_pub_key('night'); const lightPrivate = await load_priv_key('light'); - expect(await lightPrivate.decrypt(data.light.pass)).to.be.true; const encrypted = await openpgp.encrypt({ publicKeys: [nightPublic], privateKeys: [lightPrivate], data: data.light.message + "\n" }); const message = openpgp.message.readArmored(encrypted.data);