From 3fd0fa8f6858e1dbdff9f07f3ed37fd995441aaf Mon Sep 17 00:00:00 2001 From: Daniel Huigens Date: Wed, 11 Jul 2018 18:56:45 +0200 Subject: [PATCH] Various key revocation fixes --- src/encoding/armor.js | 23 +++++--- src/key.js | 118 +++++++++++++++++----------------------- src/message.js | 14 ++++- src/openpgp.js | 17 +++--- test/general/key.js | 59 ++++++++++---------- test/general/openpgp.js | 23 ++++---- 6 files changed, 121 insertions(+), 133 deletions(-) diff --git a/src/encoding/armor.js b/src/encoding/armor.js index 24298528..c18d818c 100644 --- a/src/encoding/armor.js +++ b/src/encoding/armor.js @@ -95,9 +95,10 @@ function getType(text) { * packet block. * @author Alex * @version 2011-12-16 + * @param {String} customComment (optional) additional comment to add to the armored string * @returns {String} The header information */ -function addheader() { +function addheader(customComment) { let result = ""; if (config.show_version) { result += "Version: " + config.versionstring + '\r\n'; @@ -105,6 +106,9 @@ function addheader() { if (config.show_comment) { result += "Comment: " + config.commentstring + '\r\n'; } + if (customComment) { + result += "Comment: " + customComment + '\r\n'; + } result += '\r\n'; return result; } @@ -326,22 +330,23 @@ function dearmor(text) { * @param body * @param {Integer} partindex * @param {Integer} parttotal + * @param {String} customComment (optional) additional comment to add to the armored string * @returns {String} Armored text * @static */ -function armor(messagetype, body, partindex, parttotal) { +function armor(messagetype, body, partindex, parttotal, customComment) { const result = []; switch (messagetype) { case enums.armor.multipart_section: result.push("-----BEGIN PGP MESSAGE, PART " + partindex + "/" + parttotal + "-----\r\n"); - result.push(addheader()); + result.push(addheader(customComment)); result.push(base64.encode(body)); result.push("\r\n=" + getCheckSum(body) + "\r\n"); result.push("-----END PGP MESSAGE, PART " + partindex + "/" + parttotal + "-----\r\n"); break; case enums.armor.multipart_last: result.push("-----BEGIN PGP MESSAGE, PART " + partindex + "-----\r\n"); - result.push(addheader()); + result.push(addheader(customComment)); result.push(base64.encode(body)); result.push("\r\n=" + getCheckSum(body) + "\r\n"); result.push("-----END PGP MESSAGE, PART " + partindex + "-----\r\n"); @@ -351,35 +356,35 @@ function armor(messagetype, body, partindex, parttotal) { result.push("Hash: " + body.hash + "\r\n\r\n"); result.push(body.text.replace(/^-/mg, "- -")); result.push("\r\n-----BEGIN PGP SIGNATURE-----\r\n"); - result.push(addheader()); + result.push(addheader(customComment)); result.push(base64.encode(body.data)); result.push("\r\n=" + getCheckSum(body.data) + "\r\n"); result.push("-----END PGP SIGNATURE-----\r\n"); break; case enums.armor.message: result.push("-----BEGIN PGP MESSAGE-----\r\n"); - result.push(addheader()); + result.push(addheader(customComment)); result.push(base64.encode(body)); result.push("\r\n=" + getCheckSum(body) + "\r\n"); result.push("-----END PGP MESSAGE-----\r\n"); break; case enums.armor.public_key: result.push("-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n"); - result.push(addheader()); + result.push(addheader(customComment)); result.push(base64.encode(body)); result.push("\r\n=" + getCheckSum(body) + "\r\n"); result.push("-----END PGP PUBLIC KEY BLOCK-----\r\n\r\n"); break; case enums.armor.private_key: result.push("-----BEGIN PGP PRIVATE KEY BLOCK-----\r\n"); - result.push(addheader()); + result.push(addheader(customComment)); result.push(base64.encode(body)); result.push("\r\n=" + getCheckSum(body) + "\r\n"); result.push("-----END PGP PRIVATE KEY BLOCK-----\r\n"); break; case enums.armor.signature: result.push("-----BEGIN PGP SIGNATURE-----\r\n"); - result.push(addheader()); + result.push(addheader(customComment)); result.push(base64.encode(body)); result.push("\r\n=" + getCheckSum(body) + "\r\n"); result.push("-----END PGP SIGNATURE-----\r\n"); diff --git a/src/key.js b/src/key.js index 2649f659..f2be5e9d 100644 --- a/src/key.js +++ b/src/key.js @@ -626,27 +626,26 @@ async function mergeSignatures(source, dest, attr, checkFn) { /** * Revokes the key - * @param {module:key~Key} privateKey decrypted private key for revocation * @param {Object} reasonForRevocation optional, object indicating the reason for revocation * @param {module:enums.reasonForRevocation} reasonForRevocation.flag optional, flag indicating the reason for revocation * @param {String} reasonForRevocation.string optional, string explaining the reason for revocation * @param {Date} date optional, override the creationtime of the revocation signature * @return {module:key~Key} new key with revocation signature */ -Key.prototype.revoke = async function(privateKey, { +Key.prototype.revoke = async function({ flag: reasonForRevocationFlag=enums.reasonForRevocation.no_reason, string: reasonForRevocationString='' } = {}, date=new Date()) { - if (privateKey.primaryKey.getFingerprint() !== this.primaryKey.getFingerprint()) { - throw new Error('Private key does not match public key'); + if (this.isPublic()) { + throw new Error('Need private key for revoking'); } const dataToSign = { key: this.primaryKey }; const key = new Key(this.toPacketlist()); - key.revocationSignature = await createSignaturePacket(dataToSign, privateKey, { + key.revocationSignatures.push(await createSignaturePacket(dataToSign, null, this.primaryKey, { signatureType: enums.signature.key_revocation, reasonForRevocationFlag: enums.write(enums.reasonForRevocation, reasonForRevocationFlag), reasonForRevocationString - }, date); + }, date)); return key; }; @@ -656,30 +655,22 @@ Key.prototype.revoke = async function(privateKey, { * @return {String} armored revocation certificate */ Key.prototype.getRevocationCertificate = function() { - if (this.revocationSignature) { - const commentstring = config.commentstring; - config.commentstring = 'This is a revocation certificate'; - try { - const packetlist = new packet.List(); - packetlist.push(this.revocationSignature); - return armor.encode(enums.armor.public_key, packetlist.write()); - } finally { - // Restore comment string. armor.encode() shouldn't throw, but just to be sure it's wrapped in a try/finally - config.commentstring = commentstring; - } + if (this.revocationSignatures.length) { + const packetlist = new packet.List(); + packetlist.push(getLatestSignature(this.revocationSignatures)); + return armor.encode(enums.armor.public_key, packetlist.write(), null, null, 'This is a revocation certificate'); } }; /** * Applies a revocation certificate to a key + * This adds the first signature packet in the armored text to the key, + * if it is a valid revocation signature. * @param {String} revocationCertificate armored revocation certificate * @return {module:key~Key} new revoked key */ Key.prototype.applyRevocationCertificate = async function(revocationCertificate) { const input = armor.decode(revocationCertificate); - if (input.type !== enums.armor.public_key) { - throw new Error('Armored text not of type public key'); - } const packetlist = new packet.List(); packetlist.read(input.data); const revocationSignature = packetlist.findPacket(enums.packet.signature); @@ -696,7 +687,7 @@ Key.prototype.applyRevocationCertificate = async function(revocationCertificate) throw new Error('Could not verify revocation signature'); } const key = new Key(this.toPacketlist()); - key.revocationSignature = revocationSignature; + key.revocationSignatures.push(revocationSignature); return key; }; @@ -818,11 +809,22 @@ User.prototype.toPacketlist = function() { User.prototype.sign = async function(primaryKey, privateKeys) { const dataToSign = { userid: this.userId || this.userAttribute, key: primaryKey }; const user = new User(dataToSign.userid); - user.otherCertifications = await Promise.all(privateKeys.map(function(privateKey) { + user.otherCertifications = await Promise.all(privateKeys.map(async function(privateKey) { + if (privateKey.isPublic()) { + throw new Error('Need private key for signing'); + } if (privateKey.primaryKey.getFingerprint() === primaryKey.getFingerprint()) { throw new Error('Not implemented for self signing'); } - return createSignaturePacket(dataToSign, privateKey, { + const signingKeyPacket = await privateKey.getSigningKeyPacket(); + if (!signingKeyPacket) { + throw new Error('Could not find valid signing key packet in key ' + + privateKey.primaryKey.getKeyId().toHex()); + } + if (!signingKeyPacket.isDecrypted) { + throw new Error('Private key is not decrypted.'); + } + return createSignaturePacket(dataToSign, privateKey, signingKeyPacket, { // Most OpenPGP implementations use generic certification (0x10) signatureType: enums.signature.cert_generic, keyFlags: [enums.keyFlags.certify_keys | enums.keyFlags.sign_data] @@ -857,31 +859,21 @@ User.prototype.isRevoked = async function(primaryKey, certificate, key, date=new /** * Create signature packet * @param {Object} dataToSign Contains packets to be signed - * @param {module:key~Key} privateKey private key with decrypted secret key data for signing + * @param {module:packet.SecretKey| + * module:packet.SecretSubkey} signingKeyPacket secret key packet for signing * @param {Object} signatureProperties (optional) properties to write on the signature packet before signing * @param {Date} date (optional) override the creationtime of the signature * @param {Object} userId (optional) user ID * @return {module:packet/signature} signature packet */ -export async function createSignaturePacket(dataToSign, privateKey, signatureProperties, date, userId) { - if (privateKey.isPublic()) { - throw new Error('Need private key for signing'); - } - await privateKey.verifyPrimaryUser(); - const signingKeyPacket = await privateKey.getSigningKeyPacket(undefined, date, userId); - if (!signingKeyPacket) { - throw new Error(`Could not find valid signing key packet in key ${ - privateKey.primaryKey.getKeyId().toHex()}`); - } +export async function createSignaturePacket(dataToSign, privateKey, signingKeyPacket, signatureProperties, date, userId) { if (!signingKeyPacket.isDecrypted) { throw new Error('Private key is not decrypted.'); } const signaturePacket = new packet.Signature(date); - for(const [prop, value] of Object.entries(signatureProperties)) { - signaturePacket[prop] = value; - } + Object.assign(signaturePacket, signatureProperties); signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm; - signaturePacket.hashAlgorithm = await getPreferredHashAlgo(privateKey, date, userId); + signaturePacket.hashAlgorithm = await getPreferredHashAlgo(privateKey, signingKeyPacket, date, userId); await signaturePacket.sign(signingKeyPacket, dataToSign); return signaturePacket; } @@ -1125,28 +1117,24 @@ SubKey.prototype.update = async function(subKey, primaryKey) { /** * Revokes the subkey - * @param {module:packet/signature} primaryKey primary key used for revocation - * @param {module:key~Key} privateKey decrypted private key for revocation + * @param {module:packet.SecretKey} primaryKey decrypted private primary key for revocation * @param {Object} reasonForRevocation optional, object indicating the reason for revocation * @param {module:enums.reasonForRevocation} reasonForRevocation.flag optional, flag indicating the reason for revocation * @param {String} reasonForRevocation.string optional, string explaining the reason for revocation * @param {Date} date optional, override the creationtime of the revocation signature * @return {module:key~SubKey} new subkey with revocation signature */ -SubKey.prototype.revoke = async function(primaryKey, privateKey, { +SubKey.prototype.revoke = async function(primaryKey, { flag: reasonForRevocationFlag=enums.reasonForRevocation.no_reason, string: reasonForRevocationString='' } = {}, date=new Date()) { - if (privateKey.primaryKey.getFingerprint() !== primaryKey.getFingerprint()) { - throw new Error('Private key does not match public key'); - } const dataToSign = { key: primaryKey, bind: this.subKey }; const subKey = new SubKey(this.subKey); - subKey.revocationSignature = await createSignaturePacket(dataToSign, privateKey, { + subKey.revocationSignatures.push(await createSignaturePacket(dataToSign, null, primaryKey, { signatureType: enums.signature.subkey_revocation, reasonForRevocationFlag: enums.write(enums.reasonForRevocation, reasonForRevocationFlag), reasonForRevocationString - }, date); + }, date)); await subKey.update(this, primaryKey); return subKey; }; @@ -1223,7 +1211,6 @@ export function readArmored(armoredText) { * @param {Date} date Override the creation date of the key and the key signatures * @param {Array} subkeys (optional) options for each subkey, default to main key options. e.g. [{sign: true, passphrase: '123'}] * sign parameter defaults to false, and indicates whether the subkey should sign rather than encrypt - * @param {Boolean} [options.revoked=false] Whether the key should include a revocation signature * @returns {Promise} * @async * @static @@ -1384,7 +1371,7 @@ async function wrapKeyObject(secretKeyPacket, secretSubkeyPackets, options) { const signaturePacket = new packet.Signature(options.date); signaturePacket.signatureType = enums.signature.cert_generic; signaturePacket.publicKeyAlgorithm = secretKeyPacket.algorithm; - signaturePacket.hashAlgorithm = await getPreferredHashAlgo(secretKeyPacket); + signaturePacket.hashAlgorithm = await getPreferredHashAlgo(null, secretKeyPacket); signaturePacket.keyFlags = [enums.keyFlags.certify_keys | enums.keyFlags.sign_data]; signaturePacket.preferredSymmetricAlgorithms = []; // prefer aes256, aes128, then aes192 (no WebCrypto support: https://www.chromium.org/blink/webcrypto#TOC-AES-support) @@ -1440,7 +1427,7 @@ async function wrapKeyObject(secretKeyPacket, secretSubkeyPackets, options) { const subkeySignaturePacket = new packet.Signature(subkeyOptions.date); subkeySignaturePacket.signatureType = enums.signature.subkey_binding; subkeySignaturePacket.publicKeyAlgorithm = secretKeyPacket.algorithm; - subkeySignaturePacket.hashAlgorithm = await getPreferredHashAlgo(secretSubkeyPacket); + subkeySignaturePacket.hashAlgorithm = await getPreferredHashAlgo(null, secretSubkeyPacket); subkeySignaturePacket.keyFlags = subkeyOptions.sign ? enums.keyFlags.sign_data : [enums.keyFlags.encrypt_communication | enums.keyFlags.encrypt_storage]; if (subkeyOptions.keyExpirationTime > 0) { subkeySignaturePacket.keyExpirationTime = subkeyOptions.keyExpirationTime; @@ -1456,18 +1443,14 @@ async function wrapKeyObject(secretKeyPacket, secretSubkeyPackets, options) { }); }); - if (options.revoked) { - const dataToSign = {}; - dataToSign.key = secretKeyPacket; - const revocationSignaturePacket = new packet.Signature(); - revocationSignaturePacket.signatureType = enums.signature.key_revocation; - revocationSignaturePacket.reasonForRevocationFlag = enums.reasonForRevocation.no_reason; - revocationSignaturePacket.reasonForRevocationString = ''; - revocationSignaturePacket.publicKeyAlgorithm = options.keyType; - revocationSignaturePacket.hashAlgorithm = getPreferredHashAlgo(secretKeyPacket); - await revocationSignaturePacket.sign(secretKeyPacket, dataToSign); - packetlist.push(revocationSignaturePacket); - } + // Add revocation signature packet for creating a revocation certificate. + // This packet should be removed before returning the key. + const dataToSign = { key: secretKeyPacket }; + packetlist.push(await createSignaturePacket(dataToSign, null, secretKeyPacket, { + signatureType: enums.signature.key_revocation, + reasonForRevocationFlag: enums.reasonForRevocation.no_reason, + reasonForRevocationString: '' + }, options.date)); // set passphrase protection if (options.passphrase) { @@ -1546,13 +1529,14 @@ function getExpirationTime(keyPacket, signature) { /** * Returns the preferred signature hash algorithm of a key - * @param {object} key + * @param {module:key.Key} key (optional) the key to get preferences from + * @param {module:packet.SecretKey|module:packet.SecretSubkey} keyPacket key packet used for signing * @param {Date} date (optional) use the given date for verification instead of the current time * @param {Object} userId (optional) user ID * @returns {Promise} * @async */ -export async function getPreferredHashAlgo(key, date=new Date(), userId={}) { +export async function getPreferredHashAlgo(key, keyPacket, date=new Date(), userId={}) { let hash_algo = config.prefer_hash_algorithm; let pref_algo = hash_algo; if (key instanceof Key) { @@ -1562,19 +1546,17 @@ export async function getPreferredHashAlgo(key, date=new Date(), userId={}) { hash_algo = crypto.hash.getHashByteLength(hash_algo) <= crypto.hash.getHashByteLength(pref_algo) ? pref_algo : hash_algo; } - // disable expiration checks - key = key.getSigningKeyPacket(undefined, null, userId); } - switch (Object.getPrototypeOf(key)) { + switch (Object.getPrototypeOf(keyPacket)) { case packet.SecretKey.prototype: case packet.PublicKey.prototype: case packet.SecretSubkey.prototype: case packet.PublicSubkey.prototype: - switch (key.algorithm) { + switch (keyPacket.algorithm) { case 'ecdh': case 'ecdsa': case 'eddsa': - pref_algo = crypto.publicKey.elliptic.getPreferredHashAlgo(key.params[0]); + pref_algo = crypto.publicKey.elliptic.getPreferredHashAlgo(keyPacket.params[0]); } } return crypto.hash.getHashByteLength(hash_algo) <= crypto.hash.getHashByteLength(pref_algo) ? diff --git a/src/message.js b/src/message.js index ffba8a94..cdeb888f 100644 --- a/src/message.js +++ b/src/message.js @@ -428,7 +428,7 @@ Message.prototype.sign = async function(privateKeys=[], signature=null, date=new } const onePassSig = new packet.OnePassSignature(); onePassSig.type = signatureType; - onePassSig.hashAlgorithm = await getPreferredHashAlgo(privateKey, date, userId); + onePassSig.hashAlgorithm = await getPreferredHashAlgo(privateKey, signingKeyPacket, date, userId); onePassSig.publicKeyAlgorithm = signingKeyPacket.algorithm; onePassSig.signingKeyId = signingKeyPacket.getKeyId(); if (i === privateKeys.length - 1) { @@ -499,8 +499,16 @@ export async function createSignaturePackets(literalDataPacket, privateKeys, sig const signatureType = literalDataPacket.text === null ? enums.signature.binary : enums.signature.text; - await Promise.all(privateKeys.map(privateKey => { - return createSignaturePacket(literalDataPacket, privateKey, {signatureType}, date, userId); + await Promise.all(privateKeys.map(async privateKey => { + if (privateKey.isPublic()) { + throw new Error('Need private key for signing'); + } + const signingKeyPacket = await privateKey.getSigningKeyPacket(undefined, date, userId); + if (!signingKeyPacket) { + throw new Error(`Could not find valid signing key packet in key ${ + privateKey.primaryKey.getKeyId().toHex()}`); + } + return createSignaturePacket(literalDataPacket, privateKey, signingKeyPacket, {signatureType}, date, userId); })).then(signatureList => { signatureList.forEach(signaturePacket => packetlist.push(signaturePacket)); }); diff --git a/src/openpgp.js b/src/openpgp.js index 37c279a1..9517837e 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -108,16 +108,15 @@ export function destroyWorker() { * @param {Date} date (optional) override the creation date of the key and the key signatures * @param {Array} subkeys (optional) options for each subkey, default to main key options. e.g. [{sign: true, passphrase: '123'}] * sign parameter defaults to false, and indicates whether the subkey should sign rather than encrypt - * @param {Boolean} revocationCertificate (optional) Whether the returned object should include a revocation certificate to revoke the public key * @returns {Promise} The generated key object in the form: * { key:Key, privateKeyArmored:String, publicKeyArmored:String, revocationCertificate:String } * @async * @static */ -export function generateKey({ userIds=[], passphrase="", numBits=2048, keyExpirationTime=0, curve="", date=new Date(), subkeys=[{}], revocationCertificate=true }) { +export function generateKey({ userIds=[], passphrase="", numBits=2048, keyExpirationTime=0, curve="", date=new Date(), subkeys=[{}] }) { userIds = toArray(userIds); - const options = { userIds, passphrase, numBits, keyExpirationTime, curve, date, subkeys, revocationCertificate }; + const options = { userIds, passphrase, numBits, keyExpirationTime, curve, date, subkeys }; if (util.getWebCryptoAll() && numBits < 2048) { throw new Error('numBits should be 2048 or 4096, found: ' + numBits); } @@ -126,11 +125,9 @@ export function generateKey({ userIds=[], passphrase="", numBits=2048, keyExpira return asyncProxy.delegate('generateKey', options); } - options.revoked = options.revocationCertificate; - return generate(options).then(key => { const revocationCertificate = key.getRevocationCertificate(); - key.revocationSignature = null; + key.revocationSignatures = []; return { @@ -157,7 +154,7 @@ export function generateKey({ userIds=[], passphrase="", numBits=2048, keyExpira */ export function reformatKey({privateKey, userIds=[], passphrase="", keyExpirationTime=0, date, revocationCertificate=true}) { userIds = toArray(userIds); - const options = { privateKey, userIds, passphrase, keyExpirationTime, date, revocationCertificate=true}; + const options = { privateKey, userIds, passphrase, keyExpirationTime, date, revocationCertificate }; if (asyncProxy) { return asyncProxy.delegate('reformatKey', options); } @@ -166,7 +163,7 @@ export function reformatKey({privateKey, userIds=[], passphrase="", keyExpiratio return reformat(options).then(key => { const revocationCertificate = key.getRevocationCertificate(); - key.revocationSignature = null; + key.revocationSignatures = []; return { @@ -207,10 +204,10 @@ export function revokeKey({ if (revocationCertificate) { return key.applyRevocationCertificate(revocationCertificate); } else { - return key.revoke(key, reasonForRevocation); + return key.revoke(reasonForRevocation); } }).then(key => { - if(key.isPrivate()) { + if (key.isPrivate()) { const publicKey = key.toPublic(); return { privateKey: key, diff --git a/test/general/key.js b/test/general/key.js index 4d6f319f..e208a2d7 100644 --- a/test/general/key.js +++ b/test/general/key.js @@ -114,7 +114,7 @@ function tests() { 'hz3tYjKhoFTKEIq3y3Pp', '=h/aX', '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'); - + const pub_key_arm4 = `-----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG 2.1.15 (GNU/Linux) @@ -1329,7 +1329,7 @@ const mergeKey2 = '-----BEGIN PGP PUBLIC KEY BLOCK-----\n' + }); it('Verify status of revoked primary key', function(done) { - const pubKey = openpgp.key.readArmored(pub_revoked).keys[0]; + const pubKey = openpgp.key.readArmored(pub_revoked_subkeys).keys[0]; expect(pubKey.verifyPrimaryKey()).to.eventually.equal(openpgp.enums.keyStatus.revoked).notify(done); }); @@ -1520,41 +1520,40 @@ const mergeKey2 = '-----BEGIN PGP PUBLIC KEY BLOCK-----\n' + }); }); - it('revoke() - primary key', function(done) { - const pubKey = openpgp.key.readArmored(pub_key_arm2).keys[0]; + it('revoke() - primary key', async function() { const privKey = openpgp.key.readArmored(priv_key_arm2).keys[0]; - privKey.decrypt('hello world'); + await privKey.decrypt('hello world'); - pubKey.revoke(privKey, { + await privKey.revoke({ flag: openpgp.enums.reasonForRevocation.key_retired, string: 'Testing key revocation' - }).then(revKey => { - expect(revKey.revocationSignature).to.exist; - expect(revKey.revocationSignature.signatureType).to.equal(openpgp.enums.signature.key_revocation); - expect(revKey.revocationSignature.reasonForRevocationFlag).to.equal(openpgp.enums.reasonForRevocation.key_retired); - expect(revKey.revocationSignature.reasonForRevocationString).to.equal('Testing key revocation'); + }).then(async revKey => { + expect(revKey.revocationSignatures).to.exist.and.have.length(1); + expect(revKey.revocationSignatures[0].signatureType).to.equal(openpgp.enums.signature.key_revocation); + expect(revKey.revocationSignatures[0].reasonForRevocationFlag).to.equal(openpgp.enums.reasonForRevocation.key_retired); + expect(revKey.revocationSignatures[0].reasonForRevocationString).to.equal('Testing key revocation'); - expect(pubKey.verifyPrimaryKey()).to.eventually.equal(openpgp.enums.keyStatus.valid); - expect(revKey.verifyPrimaryKey()).to.eventually.equal(openpgp.enums.keyStatus.revoked).notify(done); + expect(await privKey.verifyPrimaryKey()).to.equal(openpgp.enums.keyStatus.valid); + expect(await revKey.verifyPrimaryKey()).to.equal(openpgp.enums.keyStatus.revoked); }); }); - it('revoke() - subkey', function(done) { + it('revoke() - subkey', async function() { const pubKey = openpgp.key.readArmored(pub_key_arm2).keys[0]; const privKey = openpgp.key.readArmored(priv_key_arm2).keys[0]; - privKey.decrypt('hello world'); + await privKey.decrypt('hello world'); const subKey = pubKey.subKeys[0]; - subKey.revoke(pubKey.primaryKey, privKey, { + await subKey.revoke(privKey.primaryKey, { flag: openpgp.enums.reasonForRevocation.key_superseded - }).then(revKey => { - expect(revKey.revocationSignature).to.exist; - expect(revKey.revocationSignature.signatureType).to.equal(openpgp.enums.signature.subkey_revocation); - expect(revKey.revocationSignature.reasonForRevocationFlag).to.equal(openpgp.enums.reasonForRevocation.key_superseded); - expect(revKey.revocationSignature.reasonForRevocationString).to.equal(''); + }).then(async revKey => { + expect(revKey.revocationSignatures).to.exist.and.have.length(1); + expect(revKey.revocationSignatures[0].signatureType).to.equal(openpgp.enums.signature.subkey_revocation); + expect(revKey.revocationSignatures[0].reasonForRevocationFlag).to.equal(openpgp.enums.reasonForRevocation.key_superseded); + expect(revKey.revocationSignatures[0].reasonForRevocationString).to.equal(''); - expect(subKey.verify(pubKey.primaryKey)).to.eventually.equal(openpgp.enums.keyStatus.valid); - expect(revKey.verify(pubKey.primaryKey)).to.eventually.equal(openpgp.enums.keyStatus.revoked).notify(done); + expect(await subKey.verify(pubKey.primaryKey)).to.equal(openpgp.enums.keyStatus.valid); + expect(await revKey.verify(pubKey.primaryKey)).to.equal(openpgp.enums.keyStatus.revoked); }); }); @@ -1575,7 +1574,7 @@ const mergeKey2 = '-----BEGIN PGP PUBLIC KEY BLOCK-----\n' + packetlist.read(input.data); const armored = openpgp.armor.encode(openpgp.enums.armor.public_key, packetlist.write()); - expect(revocationCertificate.replace(/^Comment: .*$/m, '')).to.equal(armored.replace(/^Comment: .*$/m, '')); + expect(revocationCertificate.replace(/^Comment: .*$\r\n/mg, '')).to.equal(armored.replace(/^Comment: .*$\r\n/mg, '')); }); it('getRevocationCertificate() should have an appropriate comment', function() { @@ -2137,8 +2136,8 @@ const mergeKey2 = '-----BEGIN PGP PUBLIC KEY BLOCK-----\n' + return openpgp.generateKey(opt).then(function(original) { return openpgp.revokeKey({key: original.key.toPublic(), revocationCertificate: original.revocationCertificate}).then(function(revKey) { revKey = revKey.publicKey; - expect(revKey.revocationSignature.reasonForRevocationFlag).to.equal(openpgp.enums.reasonForRevocation.no_reason); - expect(revKey.revocationSignature.reasonForRevocationString).to.equal(''); + expect(revKey.revocationSignatures[0].reasonForRevocationFlag).to.equal(openpgp.enums.reasonForRevocation.no_reason); + expect(revKey.revocationSignatures[0].reasonForRevocationString).to.equal(''); return revKey.verifyPrimaryKey().then(function(status) { expect(status).to.equal(openpgp.enums.keyStatus.revoked); }); @@ -2149,12 +2148,12 @@ const mergeKey2 = '-----BEGIN PGP PUBLIC KEY BLOCK-----\n' + it('Revoke generated key with private key', function() { const opt = {numBits: 512, userIds: 'test1 ', passphrase: '1234'}; if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys - return openpgp.generateKey(opt).then(function(original) { - original.key.decrypt('1234'); + return openpgp.generateKey(opt).then(async function(original) { + await original.key.decrypt('1234'); return openpgp.revokeKey({key: original.key, reasonForRevocation: {string: 'Testing key revocation'}}).then(function(revKey) { revKey = revKey.publicKey; - expect(revKey.revocationSignature.reasonForRevocationFlag).to.equal(openpgp.enums.reasonForRevocation.no_reason); - expect(revKey.revocationSignature.reasonForRevocationString).to.equal('Testing key revocation'); + expect(revKey.revocationSignatures[0].reasonForRevocationFlag).to.equal(openpgp.enums.reasonForRevocation.no_reason); + expect(revKey.revocationSignatures[0].reasonForRevocationString).to.equal('Testing key revocation'); return revKey.verifyPrimaryKey().then(function(status) { expect(status).to.equal(openpgp.enums.keyStatus.revoked); }); diff --git a/test/general/openpgp.js b/test/general/openpgp.js index 4b9f9aac..536a12be 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -485,10 +485,7 @@ describe('OpenPGP.js public api tests', function() { } }; }, - getRevocationCertificate: function() {}, - removeRevocationCertificate: function() { - return this; - } + getRevocationCertificate: function() {} }; keyGenStub = stub(openpgp.key, 'generate'); keyGenStub.returns(resolves(keyObjStub)); @@ -517,9 +514,7 @@ describe('OpenPGP.js public api tests', function() { keyExpirationTime: 0, curve: "", date: now, - subkeys: [], - revocationCertificate: true, - revoked: true, + subkeys: [] }).calledOnce).to.be.true; expect(newKey.key).to.exist; expect(newKey.privateKeyArmored).to.exist; @@ -1862,7 +1857,7 @@ describe('OpenPGP.js public api tests', function() { }); }); - it.skip('should fail to encrypt with revoked key', function() { + it('should fail to encrypt with revoked key', function() { return openpgp.revokeKey({ key: privateKey.keys[0] }).then(function(revKey) { @@ -1877,13 +1872,15 @@ describe('OpenPGP.js public api tests', function() { }); }); - it.skip('should fail to encrypt with revoked subkey', function() { - let clonedKey = privateKey.keys[0].toPublic(); - return clonedKey.subKeys[0].revoke(clonedKey.primaryKey, privateKey.keys[0]).then(function(revSubKey) { - clonedKey.subKeys[0] = revSubKey; + it('should fail to encrypt with revoked subkey', async function() { + const pubKeyDE = openpgp.key.readArmored(pub_key_de).keys[0]; + const privKeyDE = openpgp.key.readArmored(priv_key_de).keys[0]; + await privKeyDE.decrypt(passphrase); + return privKeyDE.subKeys[0].revoke(privKeyDE.primaryKey).then(function(revSubKey) { + pubKeyDE.subKeys[0] = revSubKey; return openpgp.encrypt({ data: plaintext, - publicKeys: clonedKey + publicKeys: pubKeyDE }).then(function(encrypted) { throw new Error('Should not encrypt with revoked subkey'); }).catch(function(error) {