diff --git a/src/config/config.js b/src/config/config.js index 3599ad33..39d34a12 100644 --- a/src/config/config.js +++ b/src/config/config.js @@ -210,5 +210,15 @@ export default { * @memberof module:config * @property {Object} indutny_elliptic_fetch_options Options object to pass to `fetch` when loading the indutny/elliptic library. Only has an effect if `config.external_indutny_elliptic` is true. */ - indutny_elliptic_fetch_options: {} + indutny_elliptic_fetch_options: {}, + /** + * @memberof module:config + * @property {Set} reject_hash_algorithms Reject insecure hash algorithms {@link module:enums.hash} + */ + reject_hash_algorithms: new Set([enums.hash.md5, enums.hash.ripemd]), + /** + * @memberof module:config + * @property {Set} reject_message_hash_algorithms Reject insecure message hash algorithms {@link module:enums.hash} + */ + reject_message_hash_algorithms: new Set([enums.hash.md5, enums.hash.ripemd, enums.hash.sha1]) }; diff --git a/src/enums.js b/src/enums.js index 34ee4e2a..3762ee34 100644 --- a/src/enums.js +++ b/src/enums.js @@ -403,18 +403,6 @@ export default { shared_private_key: 128 }, - /** Key status - * @enum {Integer} - * @readonly - */ - keyStatus: { - invalid: 0, - expired: 1, - revoked: 2, - valid: 3, - no_self_cert: 4 - }, - /** Armor type * @enum {Integer} * @readonly diff --git a/src/key/factory.js b/src/key/factory.js index 292c8370..0cb6920e 100644 --- a/src/key/factory.js +++ b/src/key/factory.js @@ -111,8 +111,8 @@ export async function reformat(options) { if (!options.subkeys) { options.subkeys = await Promise.all(secretSubkeyPackets.map(async secretSubkeyPacket => ({ - sign: await options.privateKey.getSigningKey(secretSubkeyPacket.getKeyId(), null) && - !await options.privateKey.getEncryptionKey(secretSubkeyPacket.getKeyId(), null) + sign: await options.privateKey.getSigningKey(secretSubkeyPacket.getKeyId(), null).catch(() => {}) && + !await options.privateKey.getEncryptionKey(secretSubkeyPacket.getKeyId(), null).catch(() => {}) }))); } diff --git a/src/key/helper.js b/src/key/helper.js index 3e2656b3..ed3c5427 100644 --- a/src/key/helper.js +++ b/src/key/helper.js @@ -38,17 +38,29 @@ export async function generateSecretKey(options) { */ export async function getLatestValidSignature(signatures, primaryKey, signatureType, dataToVerify, date = new Date()) { let signature; + let exception; for (let i = signatures.length - 1; i >= 0; i--) { - if ( - (!signature || signatures[i].created >= signature.created) && - // check binding signature is not expired (ie, check for V4 expiration time) - !signatures[i].isExpired(date) && - // check binding signature is verified - (signatures[i].verified || await signatures[i].verify(primaryKey, signatureType, dataToVerify)) - ) { - signature = signatures[i]; + try { + if ( + (!signature || signatures[i].created >= signature.created) && + // check binding signature is not expired (ie, check for V4 expiration time) + !signatures[i].isExpired(date) && + // check binding signature is verified + (signatures[i].verified || await signatures[i].verify(primaryKey, signatureType, dataToVerify)) + ) { + signature = signatures[i]; + } + } catch (e) { + exception = e; } } + if (!signature) { + throw util.wrapError( + `Could not find valid ${enums.read(enums.signature, signatureType)} signature in key ${primaryKey.getKeyId().toHex()}` + .replace('cert_generic ', 'self-') + .replace('_', ' ') + , exception); + } return signature; } @@ -106,7 +118,7 @@ export async function getPreferredHashAlgo(key, keyPacket, date = new Date(), us let pref_algo = hash_algo; if (key) { const primaryUser = await key.getPrimaryUser(date, userId); - if (primaryUser && primaryUser.selfCertification.preferredHashAlgorithms) { + if (primaryUser.selfCertification.preferredHashAlgorithms) { [pref_algo] = primaryUser.selfCertification.preferredHashAlgorithms; hash_algo = crypto.hash.getHashByteLength(hash_algo) <= crypto.hash.getHashByteLength(pref_algo) ? pref_algo : hash_algo; @@ -143,7 +155,7 @@ export async function getPreferredAlgo(type, keys, date = new Date(), userIds = const prioMap = {}; await Promise.all(keys.map(async function(key, i) { const primaryUser = await key.getPrimaryUser(date, userIds[i]); - if (!primaryUser || !primaryUser.selfCertification[prefProperty]) { + if (!primaryUser.selfCertification[prefProperty]) { return defaultAlgo; } primaryUser.selfCertification[prefProperty].forEach(function(algo, index) { @@ -237,24 +249,24 @@ export async function isDataRevoked(primaryKey, signatureType, dataToVerify, rev const normDate = util.normalizeDate(date); const revocationKeyIds = []; await Promise.all(revocations.map(async function(revocationSignature) { - if ( - // Note: a third-party revocation signature could legitimately revoke a - // self-signature if the signature has an authorized revocation key. - // However, we don't support passing authorized revocation keys, nor - // verifying such revocation signatures. Instead, we indicate an error - // when parsing a key with an authorized revocation key, and ignore - // third-party revocation signatures here. (It could also be revoking a - // third-party key certification, which should only affect - // `verifyAllCertifications`.) - (!signature || revocationSignature.issuerKeyId.equals(signature.issuerKeyId)) && - !(config.revocations_expire && revocationSignature.isExpired(normDate)) && - (revocationSignature.verified || await revocationSignature.verify(key, signatureType, dataToVerify)) - ) { - // TODO get an identifier of the revoked object instead - revocationKeyIds.push(revocationSignature.issuerKeyId); - return true; - } - return false; + try { + if ( + // Note: a third-party revocation signature could legitimately revoke a + // self-signature if the signature has an authorized revocation key. + // However, we don't support passing authorized revocation keys, nor + // verifying such revocation signatures. Instead, we indicate an error + // when parsing a key with an authorized revocation key, and ignore + // third-party revocation signatures here. (It could also be revoking a + // third-party key certification, which should only affect + // `verifyAllCertifications`.) + (!signature || revocationSignature.issuerKeyId.equals(signature.issuerKeyId)) && + !(config.revocations_expire && revocationSignature.isExpired(normDate)) && + (revocationSignature.verified || await revocationSignature.verify(key, signatureType, dataToVerify)) + ) { + // TODO get an identifier of the revoked object instead + revocationKeyIds.push(revocationSignature.issuerKeyId); + } + } catch (e) {} })); // TODO further verify that this is the signature that should be revoked if (signature) { @@ -287,7 +299,7 @@ export async function isAeadSupported(keys, date = new Date(), userIds = []) { // TODO replace when Promise.some or Promise.any are implemented await Promise.all(keys.map(async function(key, i) { const primaryUser = await key.getPrimaryUser(date, userIds[i]); - if (!primaryUser || !primaryUser.selfCertification.features || + if (!primaryUser.selfCertification.features || !(primaryUser.selfCertification.features[0] & enums.features.aead)) { supported = false; } diff --git a/src/key/key.js b/src/key/key.js index 3b404c66..4d391eac 100644 --- a/src/key/key.js +++ b/src/key/key.js @@ -271,32 +271,35 @@ Key.prototype.armor = function() { * @async */ Key.prototype.getSigningKey = async function (keyId = null, date = new Date(), userId = {}) { + await this.verifyPrimaryKey(date, userId); const primaryKey = this.keyPacket; - if (await this.verifyPrimaryKey(date, userId) === enums.keyStatus.valid) { - const subKeys = this.subKeys.slice().sort((a, b) => b.keyPacket.created - a.keyPacket.created); - for (let i = 0; i < subKeys.length; i++) { - if (!keyId || subKeys[i].getKeyId().equals(keyId)) { - if (await subKeys[i].verify(primaryKey, date) === enums.keyStatus.valid) { - const dataToVerify = { key: primaryKey, bind: subKeys[i].keyPacket }; - const bindingSignature = await helper.getLatestValidSignature(subKeys[i].bindingSignatures, primaryKey, enums.signature.subkey_binding, dataToVerify, date); - if ( - bindingSignature && - bindingSignature.embeddedSignature && - helper.isValidSigningKeyPacket(subKeys[i].keyPacket, bindingSignature) && - await helper.getLatestValidSignature([bindingSignature.embeddedSignature], subKeys[i].keyPacket, enums.signature.key_binding, dataToVerify, date) - ) { - return subKeys[i]; - } + const subKeys = this.subKeys.slice().sort((a, b) => b.keyPacket.created - a.keyPacket.created); + let exception; + for (let i = 0; i < subKeys.length; i++) { + if (!keyId || subKeys[i].getKeyId().equals(keyId)) { + try { + await subKeys[i].verify(primaryKey, date); + const dataToVerify = { key: primaryKey, bind: subKeys[i].keyPacket }; + const bindingSignature = await helper.getLatestValidSignature(subKeys[i].bindingSignatures, primaryKey, enums.signature.subkey_binding, dataToVerify, date); + if ( + bindingSignature && + bindingSignature.embeddedSignature && + helper.isValidSigningKeyPacket(subKeys[i].keyPacket, bindingSignature) && + await helper.getLatestValidSignature([bindingSignature.embeddedSignature], subKeys[i].keyPacket, enums.signature.key_binding, dataToVerify, date) + ) { + return subKeys[i]; } + } catch (e) { + exception = e; } } - const primaryUser = await this.getPrimaryUser(date, userId); - if (primaryUser && (!keyId || primaryKey.getKeyId().equals(keyId)) && - helper.isValidSigningKeyPacket(primaryKey, primaryUser.selfCertification)) { - return this; - } } - return null; + const primaryUser = await this.getPrimaryUser(date, userId); + if ((!keyId || primaryKey.getKeyId().equals(keyId)) && + helper.isValidSigningKeyPacket(primaryKey, primaryUser.selfCertification)) { + return this; + } + throw util.wrapError('Could not find valid signing key packet in key ' + this.getKeyId().toHex(), exception); }; /** @@ -308,30 +311,32 @@ Key.prototype.getSigningKey = async function (keyId = null, date = new Date(), u * @async */ Key.prototype.getEncryptionKey = async function(keyId, date = new Date(), userId = {}) { + await this.verifyPrimaryKey(date, userId); const primaryKey = this.keyPacket; - if (await this.verifyPrimaryKey(date, userId) === enums.keyStatus.valid) { - // V4: by convention subkeys are preferred for encryption service - const subKeys = this.subKeys.slice().sort((a, b) => b.keyPacket.created - a.keyPacket.created); - for (let i = 0; i < subKeys.length; i++) { - if (!keyId || subKeys[i].getKeyId().equals(keyId)) { - if (await subKeys[i].verify(primaryKey, date) === enums.keyStatus.valid) { - const dataToVerify = { key: primaryKey, bind: subKeys[i].keyPacket }; - const bindingSignature = await helper.getLatestValidSignature(subKeys[i].bindingSignatures, primaryKey, enums.signature.subkey_binding, dataToVerify, date); - if (bindingSignature && helper.isValidEncryptionKeyPacket(subKeys[i].keyPacket, bindingSignature)) { - return subKeys[i]; - } + // V4: by convention subkeys are preferred for encryption service + const subKeys = this.subKeys.slice().sort((a, b) => b.keyPacket.created - a.keyPacket.created); + let exception; + for (let i = 0; i < subKeys.length; i++) { + if (!keyId || subKeys[i].getKeyId().equals(keyId)) { + try { + await subKeys[i].verify(primaryKey, date); + const dataToVerify = { key: primaryKey, bind: subKeys[i].keyPacket }; + const bindingSignature = await helper.getLatestValidSignature(subKeys[i].bindingSignatures, primaryKey, enums.signature.subkey_binding, dataToVerify, date); + if (bindingSignature && helper.isValidEncryptionKeyPacket(subKeys[i].keyPacket, bindingSignature)) { + return subKeys[i]; } + } catch (e) { + exception = e; } } - // if no valid subkey for encryption, evaluate primary key - const primaryUser = await this.getPrimaryUser(date, userId); - if (primaryUser && (!keyId || primaryKey.getKeyId().equals(keyId)) && - helper.isValidEncryptionKeyPacket(primaryKey, primaryUser.selfCertification)) { - return this; - } } - return null; - + // if no valid subkey for encryption, evaluate primary key + const primaryUser = await this.getPrimaryUser(date, userId); + if ((!keyId || primaryKey.getKeyId().equals(keyId)) && + helper.isValidEncryptionKeyPacket(primaryKey, primaryUser.selfCertification)) { + return this; + } + throw util.wrapError('Could not find valid encryption key packet in key ' + this.getKeyId().toHex(), exception); }; /** @@ -413,7 +418,7 @@ Key.prototype.validate = async function() { const signatureType = enums.signature.binary; signature.signatureType = signatureType; await signature.sign(signingKeyPacket, data); - return signature.verify(signingKeyPacket, signatureType, data); + await signature.verify(signingKeyPacket, signatureType, data); }; /** @@ -450,33 +455,29 @@ Key.prototype.isRevoked = async function(signature, key, date = new Date()) { /** * Verify primary key. Checks for revocation signatures, expiration time - * and valid self signature + * and valid self signature. Throws if the primary key is invalid. * @param {Date} date (optional) use the given date for verification instead of the current time * @param {Object} userId (optional) user ID - * @returns {Promise} The status of the primary key + * @returns {Promise} The status of the primary key * @async */ Key.prototype.verifyPrimaryKey = async function(date = new Date(), userId = {}) { const primaryKey = this.keyPacket; // check for key revocation signatures if (await this.isRevoked(null, null, date)) { - return enums.keyStatus.revoked; + throw new Error('Primary key is revoked'); } // check for at least one self signature. Self signature of user ID not mandatory // See {@link https://tools.ietf.org/html/rfc4880#section-11.1} if (!this.users.some(user => user.userId && user.selfCertifications.length)) { - return enums.keyStatus.no_self_cert; + throw new Error('No self-certifications'); } // check for valid, unrevoked, unexpired self signature - const { user, selfCertification } = await this.getPrimaryUser(date, userId) || {}; - if (!user) { - return enums.keyStatus.invalid; - } + const { selfCertification } = await this.getPrimaryUser(date, userId); // check for expiration time if (helper.isDataExpired(primaryKey, selfCertification, date)) { - return enums.keyStatus.expired; + throw new Error('Primary key is expired'); } - return enums.keyStatus.valid; }; /** @@ -492,25 +493,22 @@ Key.prototype.verifyPrimaryKey = async function(date = new Date(), userId = {}) */ Key.prototype.getExpirationTime = async function(capabilities, keyId, userId) { const primaryUser = await this.getPrimaryUser(null, userId); - if (!primaryUser) { - throw new Error('Could not find primary user'); - } const selfCert = primaryUser.selfCertification; const keyExpiry = helper.getExpirationTime(this.keyPacket, selfCert); const sigExpiry = selfCert.getExpirationTime(); let expiry = keyExpiry < sigExpiry ? keyExpiry : sigExpiry; if (capabilities === 'encrypt' || capabilities === 'encrypt_sign') { const encryptKey = - await this.getEncryptionKey(keyId, expiry, userId) || - await this.getEncryptionKey(keyId, null, userId); + await this.getEncryptionKey(keyId, expiry, userId).catch(() => {}) || + await this.getEncryptionKey(keyId, null, userId).catch(() => {}); if (!encryptKey) return null; const encryptExpiry = await encryptKey.getExpirationTime(this.keyPacket); if (encryptExpiry < expiry) expiry = encryptExpiry; } if (capabilities === 'sign' || capabilities === 'encrypt_sign') { const signKey = - await this.getSigningKey(keyId, expiry, userId) || - await this.getSigningKey(keyId, null, userId); + await this.getSigningKey(keyId, expiry, userId).catch(() => {}) || + await this.getSigningKey(keyId, null, userId).catch(() => {}); if (!signKey) return null; const signExpiry = await signKey.getExpirationTime(this.keyPacket); if (signExpiry < expiry) expiry = signExpiry; @@ -531,24 +529,29 @@ Key.prototype.getExpirationTime = async function(capabilities, keyId, userId) { Key.prototype.getPrimaryUser = async function(date = new Date(), userId = {}) { const primaryKey = this.keyPacket; const users = []; + let exception; for (let i = 0; i < this.users.length; i++) { - const user = this.users[i]; - if (!user.userId || !( - (userId.name === undefined || user.userId.name === userId.name) && - (userId.email === undefined || user.userId.email === userId.email) && - (userId.comment === undefined || user.userId.comment === userId.comment) - )) continue; - const dataToVerify = { userId: user.userId, key: primaryKey }; - const selfCertification = await helper.getLatestValidSignature(user.selfCertifications, primaryKey, enums.signature.cert_generic, dataToVerify, date); - if (!selfCertification) continue; - users.push({ index: i, user, selfCertification }); + try { + const user = this.users[i]; + if (!user.userId) { + continue; + } + if ( + (userId.name !== undefined && user.userId.name !== userId.name) || + (userId.email !== undefined && user.userId.email !== userId.email) || + (userId.comment !== undefined && user.userId.comment !== userId.comment) + ) { + throw new Error('Could not find user that matches that user ID'); + } + const dataToVerify = { userId: user.userId, key: primaryKey }; + const selfCertification = await helper.getLatestValidSignature(user.selfCertifications, primaryKey, enums.signature.cert_generic, dataToVerify, date); + users.push({ index: i, user, selfCertification }); + } catch (e) { + exception = e; + } } if (!users.length) { - if (userId.name !== undefined || userId.email !== undefined || - userId.comment !== undefined) { - throw new Error('Could not find user that matches that user ID'); - } - return null; + throw exception || new Error('Could not find primary user'); } await Promise.all(users.map(async function (a) { return a.user.revoked || a.user.isRevoked(primaryKey, a.selfCertification, null, date); @@ -561,7 +564,7 @@ Key.prototype.getPrimaryUser = async function(date = new Date(), userId = {}) { }).pop(); const { user, selfCertification: cert } = primaryUser; if (cert.revoked || await user.isRevoked(primaryKey, cert, null, date)) { - return null; + throw new Error('Primary user is revoked'); } return primaryUser; }; @@ -578,9 +581,6 @@ Key.prototype.getPrimaryUser = async function(date = new Date(), userId = {}) { * @async */ Key.prototype.update = async function(key) { - if (await key.verifyPrimaryKey() === enums.keyStatus.invalid) { - return; - } if (!this.hasSameFingerprintAs(key)) { throw new Error('Key update method: fingerprints of keys not equal'); } @@ -670,11 +670,9 @@ Key.prototype.revoke = async function({ Key.prototype.getRevocationCertificate = async function() { const dataToVerify = { key: this.keyPacket }; const revocationSignature = await helper.getLatestValidSignature(this.revocationSignatures, this.keyPacket, enums.signature.key_revocation, dataToVerify); - if (revocationSignature) { - const packetlist = new packet.List(); - packetlist.push(revocationSignature); - return armor.encode(enums.armor.public_key, packetlist.write(), null, null, 'This is a revocation certificate'); - } + const packetlist = new packet.List(); + packetlist.push(revocationSignature); + return armor.encode(enums.armor.public_key, packetlist.write(), null, null, 'This is a revocation certificate'); }; /** @@ -699,8 +697,10 @@ Key.prototype.applyRevocationCertificate = async function(revocationCertificate) if (revocationSignature.isExpired()) { throw new Error('Revocation signature is expired'); } - if (!await revocationSignature.verify(this.keyPacket, enums.signature.key_revocation, { key: this.keyPacket })) { - throw new Error('Could not verify revocation signature'); + try { + await revocationSignature.verify(this.keyPacket, enums.signature.key_revocation, { key: this.keyPacket }); + } catch (e) { + throw util.wrapError('Could not verify revocation signature', e); } const key = new Key(this.toPacketlist()); key.revocationSignatures.push(revocationSignature); @@ -716,10 +716,7 @@ Key.prototype.applyRevocationCertificate = async function(revocationCertificate) * @async */ Key.prototype.signPrimaryUser = async function(privateKeys, date, userId) { - const { index, user } = await this.getPrimaryUser(date, userId) || {}; - if (!user) { - throw new Error('Could not find primary user'); - } + const { index, user } = await this.getPrimaryUser(date, userId); const userSign = await user.sign(this.keyPacket, privateKeys); const key = new Key(this.toPacketlist()); key.users[index] = userSign; @@ -754,12 +751,9 @@ Key.prototype.signAllUsers = async function(privateKeys) { */ Key.prototype.verifyPrimaryUser = async function(keys, date, userId) { const primaryKey = this.keyPacket; - const { user } = await this.getPrimaryUser(date, userId) || {}; - if (!user) { - throw new Error('Could not find primary user'); - } + const { user } = await this.getPrimaryUser(date, userId); const results = keys ? await user.verifyAllCertifications(primaryKey, keys) : - [{ keyid: primaryKey.keyid, valid: await user.verify(primaryKey) === enums.keyStatus.valid }]; + [{ keyid: primaryKey.keyid, valid: await user.verify(primaryKey).catch(() => false) }]; return results; }; @@ -778,7 +772,7 @@ Key.prototype.verifyAllUsers = async function(keys) { const primaryKey = this.keyPacket; await Promise.all(this.users.map(async function(user) { const signatures = keys ? await user.verifyAllCertifications(primaryKey, keys) : - [{ keyid: primaryKey.keyid, valid: await user.verify(primaryKey) === enums.keyStatus.valid }]; + [{ keyid: primaryKey.keyid, valid: await user.verify(primaryKey).catch(() => false) }]; signatures.forEach(signature => { results.push({ userid: user.userId.userid, diff --git a/src/key/subkey.js b/src/key/subkey.js index 262427d1..27a987ee 100644 --- a/src/key/subkey.js +++ b/src/key/subkey.js @@ -65,31 +65,25 @@ SubKey.prototype.isRevoked = async function(primaryKey, signature, key, date = n /** * Verify subkey. Checks for revocation signatures, expiration time - * and valid binding signature + * and valid binding signature. Throws if the subkey is invalid. * @param {module:packet.SecretKey| * module:packet.PublicKey} primaryKey The primary key packet * @param {Date} date Use the given date instead of the current time - * @returns {Promise} The status of the subkey + * @returns {Promise} The status of the subkey * @async */ SubKey.prototype.verify = async function(primaryKey, date = new Date()) { - const that = this; const dataToVerify = { key: primaryKey, bind: this.keyPacket }; // check subkey binding signatures const bindingSignature = await helper.getLatestValidSignature(this.bindingSignatures, primaryKey, enums.signature.subkey_binding, dataToVerify, date); - // check binding signature is verified - if (!bindingSignature) { - return enums.keyStatus.invalid; - } // check binding signature is not revoked - if (bindingSignature.revoked || await that.isRevoked(primaryKey, bindingSignature, null, date)) { - return enums.keyStatus.revoked; + if (bindingSignature.revoked || await this.isRevoked(primaryKey, bindingSignature, null, date)) { + throw new Error('Subkey is revoked'); } // check for expiration time if (helper.isDataExpired(this.keyPacket, bindingSignature, date)) { - return enums.keyStatus.expired; + throw new Error('Subkey is expired'); } - return enums.keyStatus.valid; // binding signature passed all checks }; /** @@ -103,8 +97,12 @@ SubKey.prototype.verify = async function(primaryKey, date = new Date()) { */ SubKey.prototype.getExpirationTime = async function(primaryKey, date = new Date()) { const dataToVerify = { key: primaryKey, bind: this.keyPacket }; - const bindingSignature = await helper.getLatestValidSignature(this.bindingSignatures, primaryKey, enums.signature.subkey_binding, dataToVerify, date); - if (!bindingSignature) return null; + let bindingSignature; + try { + bindingSignature = await helper.getLatestValidSignature(this.bindingSignatures, primaryKey, enums.signature.subkey_binding, dataToVerify, date); + } catch (e) { + return null; + } const keyExpiry = helper.getExpirationTime(this.keyPacket, bindingSignature); const sigExpiry = bindingSignature.getExpirationTime(); return keyExpiry < sigExpiry ? keyExpiry : sigExpiry; @@ -119,9 +117,6 @@ SubKey.prototype.getExpirationTime = async function(primaryKey, date = new Date( * @async */ SubKey.prototype.update = async function(subKey, primaryKey) { - if (await subKey.verify(primaryKey) === enums.keyStatus.invalid) { - return; - } if (!this.hasSameFingerprintAs(subKey)) { throw new Error('SubKey update method: fingerprints of subkeys not equal'); } @@ -134,9 +129,6 @@ SubKey.prototype.update = async function(subKey, primaryKey) { const that = this; const dataToVerify = { key: primaryKey, bind: that.keyPacket }; await helper.mergeSignatures(subKey, this, 'bindingSignatures', async function(srcBindSig) { - if (!(srcBindSig.verified || await srcBindSig.verify(primaryKey, enums.signature.subkey_binding, dataToVerify))) { - return false; - } for (let i = 0; i < that.bindingSignatures.length; i++) { if (that.bindingSignatures[i].issuerKeyId.equals(srcBindSig.issuerKeyId)) { if (srcBindSig.created > that.bindingSignatures[i].created) { @@ -145,7 +137,11 @@ SubKey.prototype.update = async function(subKey, primaryKey) { return false; } } - return true; + try { + return srcBindSig.verified || await srcBindSig.verify(primaryKey, enums.signature.subkey_binding, dataToVerify); + } catch (e) { + return false; + } }); // revocation signatures await helper.mergeSignatures(subKey, this, 'revocationSignatures', function(srcRevSig) { diff --git a/src/key/user.js b/src/key/user.js index d2e0d7fe..84d5320a 100644 --- a/src/key/user.js +++ b/src/key/user.js @@ -1,12 +1,14 @@ /** * @requires enums + * @requires util * @requires packet * @requires key/helper * @module key/User */ -import packet from '../packet'; import enums from '../enums'; +import util from '../util'; +import packet from '../packet'; import { mergeSignatures, isDataRevoked, createSignaturePacket } from './helper'; /** @@ -60,10 +62,6 @@ User.prototype.sign = async function(primaryKey, privateKeys) { throw new Error('Not implemented for self signing'); } const signingKey = await privateKey.getSigningKey(); - if (!signingKey) { - throw new Error('Could not find valid signing key packet in key ' + - privateKey.getKeyId().toHex()); - } return createSignaturePacket(dataToSign, privateKey, signingKey.keyPacket, { // Most OpenPGP implementations use generic certification (0x10) signatureType: enums.signature.cert_generic, @@ -99,13 +97,13 @@ User.prototype.isRevoked = async function(primaryKey, certificate, key, date = n /** - * Verifies the user certificate + * Verifies the user certificate. Throws if the user certificate is invalid. * @param {module:packet.SecretKey| * module:packet.PublicKey} primaryKey The primary key packet * @param {module:packet.Signature} certificate A certificate of this user * @param {Array} keys Array of keys to verify certificate signatures * @param {Date} date Use the given date instead of the current time - * @returns {Promise} status of the certificate + * @returns {Promise} status of the certificate * @async */ User.prototype.verifyCertificate = async function(primaryKey, certificate, keys, date = new Date()) { @@ -117,20 +115,24 @@ User.prototype.verifyCertificate = async function(primaryKey, certificate, keys, key: primaryKey }; const results = await Promise.all(keys.map(async function(key) { - if (!key.getKeyIds().some(id => id.equals(keyid))) { return; } + if (!key.getKeyIds().some(id => id.equals(keyid))) { + return null; + } const signingKey = await key.getSigningKey(keyid, date); if (certificate.revoked || await that.isRevoked(primaryKey, certificate, signingKey.keyPacket, date)) { - return enums.keyStatus.revoked; + throw new Error('User certificate is revoked'); } - if (!(certificate.verified || await certificate.verify(signingKey.keyPacket, enums.signature.cert_generic, dataToVerify))) { - return enums.keyStatus.invalid; + try { + certificate.verified || await certificate.verify(signingKey.keyPacket, enums.signature.cert_generic, dataToVerify); + } catch (e) { + throw util.wrapError('User certificate is invalid', e); } if (certificate.isExpired(date)) { - return enums.keyStatus.expired; + throw new Error('User certificate is expired'); } - return enums.keyStatus.valid; + return true; })); - return results.find(result => result !== undefined); + return results.find(result => result !== null) || null; }; /** @@ -147,26 +149,25 @@ User.prototype.verifyAllCertifications = async function(primaryKey, keys, date = const that = this; const certifications = this.selfCertifications.concat(this.otherCertifications); return Promise.all(certifications.map(async function(certification) { - const status = await that.verifyCertificate(primaryKey, certification, keys, date); return { keyid: certification.issuerKeyId, - valid: status === undefined ? null : status === enums.keyStatus.valid + valid: await that.verifyCertificate(primaryKey, certification, keys, date).catch(() => false) }; })); }; /** * Verify User. Checks for existence of self signatures, revocation signatures - * and validity of self signature + * and validity of self signature. Throws when there are no valid self signatures. * @param {module:packet.SecretKey| * module:packet.PublicKey} primaryKey The primary key packet * @param {Date} date Use the given date instead of the current time - * @returns {Promise} Status of user + * @returns {Promise} Status of user * @async */ User.prototype.verify = async function(primaryKey, date = new Date()) { if (!this.selfCertifications.length) { - return enums.keyStatus.no_self_cert; + throw new Error('No self-certifications'); } const that = this; const dataToVerify = { @@ -175,21 +176,27 @@ User.prototype.verify = async function(primaryKey, date = new Date()) { key: primaryKey }; // TODO replace when Promise.some or Promise.any are implemented - const results = [enums.keyStatus.invalid].concat( - await Promise.all(this.selfCertifications.map(async function(selfCertification) { + let exception; + for (let i = this.selfCertifications.length - 1; i >= 0; i--) { + try { + const selfCertification = this.selfCertifications[i]; if (selfCertification.revoked || await that.isRevoked(primaryKey, selfCertification, undefined, date)) { - return enums.keyStatus.revoked; + throw new Error('Self-certification is revoked'); } - if (!(selfCertification.verified || await selfCertification.verify(primaryKey, enums.signature.cert_generic, dataToVerify))) { - return enums.keyStatus.invalid; + try { + selfCertification.verified || await selfCertification.verify(primaryKey, enums.signature.cert_generic, dataToVerify); + } catch (e) { + throw util.wrapError('Self-certification is invalid', e); } if (selfCertification.isExpired(date)) { - return enums.keyStatus.expired; + throw new Error('Self-certification is expired'); } - return enums.keyStatus.valid; - }))); - return results.some(status => status === enums.keyStatus.valid) ? - enums.keyStatus.valid : results.pop(); + return true; + } catch (e) { + exception = e; + } + } + throw exception; }; /** @@ -208,7 +215,11 @@ User.prototype.update = async function(user, primaryKey) { }; // self signatures await mergeSignatures(user, this, 'selfCertifications', async function(srcSelfSig) { - return srcSelfSig.verified || srcSelfSig.verify(primaryKey, enums.signature.cert_generic, dataToVerify); + try { + return srcSelfSig.verified || srcSelfSig.verify(primaryKey, enums.signature.cert_generic, dataToVerify); + } catch (e) { + return false; + } }); // other signatures await mergeSignatures(user, this, 'otherCertifications'); diff --git a/src/message.js b/src/message.js index 1f5831aa..50256c65 100644 --- a/src/message.js +++ b/src/message.js @@ -183,16 +183,18 @@ Message.prototype.decryptSessionKeys = async function(privateKeys, passwords) { } await Promise.all(pkESKeyPacketlist.map(async function(keyPacket) { await Promise.all(privateKeys.map(async function(privateKey) { - const primaryUser = await privateKey.getPrimaryUser(); // TODO: Pass userId from somewhere. let algos = [ enums.symmetric.aes256, // Old OpenPGP.js default fallback enums.symmetric.aes128, // RFC4880bis fallback enums.symmetric.tripledes, // RFC4880 fallback enums.symmetric.cast5 // Golang OpenPGP fallback ]; - if (primaryUser && primaryUser.selfCertification.preferredSymmetricAlgorithms) { - algos = algos.concat(primaryUser.selfCertification.preferredSymmetricAlgorithms); - } + try { + const primaryUser = await privateKey.getPrimaryUser(); // TODO: Pass userId from somewhere. + if (primaryUser.selfCertification.preferredSymmetricAlgorithms) { + algos = algos.concat(primaryUser.selfCertification.preferredSymmetricAlgorithms); + } + } catch (e) {} const privateKeyPackets = privateKey.getKeys(keyPacket.publicKeyId).map(key => key.keyPacket); await Promise.all(privateKeyPackets.map(async function(privateKeyPacket) { @@ -358,10 +360,6 @@ export async function encryptSessionKey(sessionKey, symAlgo, aeadAlgo, publicKey if (publicKeys) { const results = await Promise.all(publicKeys.map(async function(publicKey) { const encryptionKey = await publicKey.getEncryptionKey(undefined, date, userIds); - if (!encryptionKey) { - throw new Error('Could not find valid key packet for encryption in key ' + - publicKey.getKeyId().toHex()); - } const pkESKeyPacket = new packet.PublicKeyEncryptedSessionKey(); pkESKeyPacket.publicKeyId = wildcard ? type_keyid.wildcard() : encryptionKey.getKeyId(); pkESKeyPacket.publicKeyAlgorithm = encryptionKey.keyPacket.algorithm; @@ -457,10 +455,6 @@ Message.prototype.sign = async function(privateKeys = [], signature = null, date throw new Error('Need private key for signing'); } const signingKey = await privateKey.getSigningKey(undefined, date, userIds); - if (!signingKey) { - throw new Error('Could not find valid key packet for signing in key ' + - privateKey.getKeyId().toHex()); - } const onePassSig = new packet.OnePassSignature(); onePassSig.signatureType = signatureType; onePassSig.hashAlgorithm = await getPreferredHashAlgo(privateKey, signingKey.keyPacket, date, userIds); @@ -543,10 +537,6 @@ export async function createSignaturePackets(literalDataPacket, privateKeys, sig throw new Error('Need private key for signing'); } const signingKey = await privateKey.getSigningKey(undefined, date, userId); - if (!signingKey) { - throw new Error(`Could not find valid signing key packet in key ${ - privateKey.getKeyId().toHex()}`); - } return createSignaturePacket(literalDataPacket, privateKey, signingKey.keyPacket, { signatureType }, date, userId, detached, streaming); })).then(signatureList => { signatureList.forEach(signaturePacket => packetlist.push(signaturePacket)); @@ -646,11 +636,10 @@ async function createVerificationObject(signature, literalDataList, keys, date = let signingKey = null; await Promise.all(keys.map(async function(key) { // Look for the unique key that matches issuerKeyId of signature - const result = await key.getSigningKey(signature.issuerKeyId, null); - if (result) { + try { + signingKey = await key.getSigningKey(signature.issuerKeyId, null); primaryKey = key; - signingKey = result; - } + } catch (e) {} })); const signaturePacket = signature.correspondingSig || signature; @@ -669,7 +658,7 @@ async function createVerificationObject(signature, literalDataList, keys, date = signingKey.getExpirationTime(primaryKey, date) ) )) { - return null; + throw new Error('Signature is expired'); } return verified; })(), diff --git a/src/openpgp.js b/src/openpgp.js index 2a5d2cab..2b7afcd6 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -683,7 +683,7 @@ async function prepareSignatures(signatures) { try { signature.valid = await signature.verified; } catch (e) { - signature.valid = null; + signature.valid = false; signature.error = e; util.print_debug_error(e); } diff --git a/src/packet/clone.js b/src/packet/clone.js index c55e7f87..fa572cdc 100644 --- a/src/packet/clone.js +++ b/src/packet/clone.js @@ -86,9 +86,11 @@ function verificationObjectToClone(verObject) { const packets = (await signature).packets; try { await verified; + } catch (e) {} + if (packets && packets[0]) { delete packets[0].signature; delete packets[0].hashed; - } catch (e) {} + } return packets; }); } else { diff --git a/src/packet/signature.js b/src/packet/signature.js index 8316adb2..6b487bff 100644 --- a/src/packet/signature.js +++ b/src/packet/signature.js @@ -706,46 +706,55 @@ Signature.prototype.verify = async function (key, signatureType, data, detached hash = await this.hash(signatureType, data, toHash); } hash = await stream.readToEnd(hash); - let verified; if (this.signedHashValue[0] !== hash[0] || this.signedHashValue[1] !== hash[1]) { - verified = false; - } else { - let mpicount = 0; - // Algorithm-Specific Fields for RSA signatures: - // - multiprecision number (MPI) of RSA signature value m**d mod n. - if (publicKeyAlgorithm > 0 && publicKeyAlgorithm < 4) { - mpicount = 1; - - // Algorithm-Specific Fields for DSA, ECDSA, and EdDSA signatures: - // - MPI of DSA value r. - // - MPI of DSA value s. - } else if (publicKeyAlgorithm === enums.publicKey.dsa || - publicKeyAlgorithm === enums.publicKey.ecdsa || - publicKeyAlgorithm === enums.publicKey.eddsa) { - mpicount = 2; - } - - // EdDSA signature parameters are encoded in little-endian format - // https://tools.ietf.org/html/rfc8032#section-5.1.2 - const endian = publicKeyAlgorithm === enums.publicKey.eddsa ? 'le' : 'be'; - const mpi = []; - let i = 0; - this.signature = await stream.readToEnd(this.signature); - for (let j = 0; j < mpicount; j++) { - mpi[j] = new type_mpi(); - i += mpi[j].read(this.signature.subarray(i, this.signature.length), endian); - } - verified = await crypto.signature.verify( - publicKeyAlgorithm, hashAlgorithm, mpi, key.params, - toHash, hash - ); - if (verified && this.revocationKeyClass !== null) { - throw new Error('This key is intended to be revoked with an authorized key, which OpenPGP.js does not support.'); - } + throw new Error('Message digest did not match'); } - this.verified = verified; - return verified; + + let mpicount = 0; + // Algorithm-Specific Fields for RSA signatures: + // - multiprecision number (MPI) of RSA signature value m**d mod n. + if (publicKeyAlgorithm > 0 && publicKeyAlgorithm < 4) { + mpicount = 1; + + // Algorithm-Specific Fields for DSA, ECDSA, and EdDSA signatures: + // - MPI of DSA value r. + // - MPI of DSA value s. + } else if (publicKeyAlgorithm === enums.publicKey.dsa || + publicKeyAlgorithm === enums.publicKey.ecdsa || + publicKeyAlgorithm === enums.publicKey.eddsa) { + mpicount = 2; + } + + // EdDSA signature parameters are encoded in little-endian format + // https://tools.ietf.org/html/rfc8032#section-5.1.2 + const endian = publicKeyAlgorithm === enums.publicKey.eddsa ? 'le' : 'be'; + const mpi = []; + let i = 0; + this.signature = await stream.readToEnd(this.signature); + for (let j = 0; j < mpicount; j++) { + mpi[j] = new type_mpi(); + i += mpi[j].read(this.signature.subarray(i, this.signature.length), endian); + } + const verified = await crypto.signature.verify( + publicKeyAlgorithm, hashAlgorithm, mpi, key.params, + toHash, hash + ); + if (!verified) { + throw new Error('Signature verification failed'); + } + if (config.reject_hash_algorithms.has(hashAlgorithm)) { + throw new Error('Insecure hash algorithm: ' + enums.read(enums.hash, hashAlgorithm).toUpperCase()); + } + if (config.reject_message_hash_algorithms.has(hashAlgorithm) && + [enums.signature.binary, enums.signature.text].includes(this.signatureType)) { + throw new Error('Insecure message hash algorithm: ' + enums.read(enums.hash, hashAlgorithm).toUpperCase()); + } + if (this.revocationKeyClass !== null) { + throw new Error('This key is intended to be revoked with an authorized key, which OpenPGP.js does not support.'); + } + this.verified = true; + return true; }; /** diff --git a/src/util.js b/src/util.js index 9fe5403e..94917d7c 100644 --- a/src/util.js +++ b/src/util.js @@ -753,5 +753,18 @@ export default { result += ALPHABET[MASK & (buffer >> bitsLeft)]; } return result; + }, + + wrapError: function(message, error) { + if (!error) { + return new Error(message); + } + + // update error message + try { + error.message = message + ': ' + error.message; + } catch (e) {} + + return error; } }; diff --git a/test/general/key.js b/test/general/key.js index ff005cc8..a13aa0a0 100644 --- a/test/general/key.js +++ b/test/general/key.js @@ -2310,13 +2310,11 @@ function versionSpecificTests() { 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) { - return openpgp.revokeKey({key: original.key.toPublic(), revocationCertificate: original.revocationCertificate}).then(function(revKey) { + return openpgp.revokeKey({key: original.key.toPublic(), revocationCertificate: original.revocationCertificate}).then(async function(revKey) { revKey = revKey.publicKey; 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); - }); + await expect(revKey.verifyPrimaryKey()).to.be.rejectedWith('Primary key is revoked'); }); }); }); @@ -2326,13 +2324,11 @@ function versionSpecificTests() { if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys 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) { + return openpgp.revokeKey({key: original.key, reasonForRevocation: {string: 'Testing key revocation'}}).then(async function(revKey) { revKey = revKey.publicKey; 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); - }); + await expect(revKey.verifyPrimaryKey()).to.be.rejectedWith('Primary key is revoked'); }); }); }); @@ -2346,7 +2342,7 @@ function versionSpecificTests() { const { keys: [key] } = await openpgp.key.readArmored(v5_sample_key); expect(key.primaryKey.getFingerprint()).to.equal('19347bc9872464025f99df3ec2e0000ed9884892e1f7b3ea4c94009159569b54'); expect(key.subKeys[0].getFingerprint()).to.equal('e4557c2b02ffbf4b04f87401ec336af7133d0f85be7fd09baefd9caeb8c93965'); - expect(await key.verifyPrimaryKey()).to.equal(openpgp.enums.keyStatus.valid); + await key.verifyPrimaryKey(); }); } @@ -2480,7 +2476,7 @@ describe('Key', function() { it('Verify status of revoked primary key', async function() { const pubKey = (await openpgp.key.readArmored(pub_revoked_subkeys)).keys[0]; - expect(pubKey.verifyPrimaryKey()).to.eventually.equal(openpgp.enums.keyStatus.revoked); + await expect(pubKey.verifyPrimaryKey()).to.be.rejectedWith('Primary key is revoked'); }); it('Verify status of revoked subkey', async function() { @@ -2496,7 +2492,7 @@ describe('Key', function() { await expect(pubKey.subKeys[0].verify( pubKey.primaryKey - )).to.eventually.equal(openpgp.enums.keyStatus.revoked); + )).to.be.rejectedWith('Subkey is revoked'); }); it('Verify status of key with non-self revocation signature', async function() { @@ -2516,16 +2512,16 @@ describe('Key', function() { expect(signatures[1].valid).to.be.false; const { user } = await pubKey.getPrimaryUser(); - expect(await user.verifyCertificate(pubKey.primaryKey, user.otherCertifications[0], [certifyingKey])).to.equal(openpgp.enums.keyStatus.revoked); + await expect(user.verifyCertificate(pubKey.primaryKey, user.otherCertifications[0], [certifyingKey])).to.be.rejectedWith('User certificate is revoked'); }); it('Verify certificate of key with future creation date', async function() { const { keys: [pubKey] } = await openpgp.key.readArmored(key_created_2030); const user = pubKey.users[0]; - expect(await user.verifyCertificate(pubKey.primaryKey, user.selfCertifications[0], [pubKey], pubKey.primaryKey.created)).to.equal(openpgp.enums.keyStatus.valid); + await user.verifyCertificate(pubKey.primaryKey, user.selfCertifications[0], [pubKey], pubKey.primaryKey.created); const verifyAllResult = await user.verifyAllCertifications(pubKey.primaryKey, [pubKey], pubKey.primaryKey.created); expect(verifyAllResult[0].valid).to.be.true; - expect(await user.verify(pubKey.primaryKey, pubKey.primaryKey.created)).to.equal(openpgp.enums.keyStatus.valid); + await user.verify(pubKey.primaryKey, pubKey.primaryKey.created); }); it('Evaluate key flags to find valid encryption key packet', async function() { @@ -2538,8 +2534,7 @@ describe('Key', function() { // remove subkeys pubKey.subKeys = []; // primary key has only key flags for signing - const encryptionKey = await pubKey.getEncryptionKey(); - expect(encryptionKey).to.not.exist; + await expect(pubKey.getEncryptionKey()).to.be.rejectedWith('Could not find valid encryption key packet in key c076e634d32b498d'); }); it('Method getExpirationTime V4 Key', async function() { @@ -2587,15 +2582,15 @@ describe('Key', function() { expect(encryptExpirationTime).to.equal(Infinity); }); - it('validate() - return true if key parameters correspond', async function() { + it("validate() - don't throw if key parameters correspond", async function() { const { key } = await openpgp.generateKey({ userIds: {}, curve: 'ed25519' }); - expect(await key.validate()).to.be.true; + await key.validate(); }); - it('validate() - return false if key parameters do not correspond', async function() { + it("validate() - throw if key parameters don't correspond", async function() { const { keys: [key] } = await openpgp.key.readArmored(mismatchingKeyParams); await key.decrypt('userpass'); - expect(await key.validate()).to.be.false; + await expect(key.validate()).to.be.rejectedWith('Signature verification failed'); }); it('clearPrivateParams() - check that private key can no longer be used', async function() { @@ -2625,7 +2620,7 @@ describe('Key', function() { const use_nativeVal = openpgp.config.use_native; openpgp.config.use_native = false; try { - expect(await key.validate()).to.be.false; + await expect(key.validate()).to.be.rejectedWith('Signature verification failed'); } finally { openpgp.config.use_native = use_nativeVal; } @@ -2743,7 +2738,7 @@ describe('Key', function() { source.subKeys = []; expect(dest.subKeys).to.exist; expect(dest.isPublic()).to.be.true; - await expect(dest.update.bind(dest, source)()) + await expect(dest.update(source)) .to.be.rejectedWith('Cannot update public key with private key if subkey mismatch'); }); @@ -2751,14 +2746,11 @@ describe('Key', function() { const source = (await openpgp.key.readArmored(pgp_desktop_pub)).keys[0]; const dest = (await openpgp.key.readArmored(pgp_desktop_priv)).keys[0]; expect(source.subKeys[0].bindingSignatures[0]).to.exist; - await expect(source.subKeys[0].verify(source.primaryKey)) - .to.eventually.equal(openpgp.enums.keyStatus.valid); + await source.subKeys[0].verify(source.primaryKey); expect(dest.subKeys[0].bindingSignatures[0]).to.not.exist; - return dest.update(source).then(async () => { - expect(dest.subKeys[0].bindingSignatures[0]).to.exist; - await expect(dest.subKeys[0].verify(source.primaryKey)) - .to.eventually.equal(openpgp.enums.keyStatus.valid); - }); + await dest.update(source); + expect(dest.subKeys[0].bindingSignatures[0]).to.exist; + await dest.subKeys[0].verify(source.primaryKey); }); it('update() - merge multiple subkey binding signatures', async function() { @@ -2788,8 +2780,8 @@ describe('Key', function() { expect(revKey.revocationSignatures[0].reasonForRevocationFlag).to.equal(openpgp.enums.reasonForRevocation.key_retired); expect(revKey.revocationSignatures[0].reasonForRevocationString).to.equal('Testing key revocation'); - expect(await privKey.verifyPrimaryKey()).to.equal(openpgp.enums.keyStatus.valid); - expect(await revKey.verifyPrimaryKey()).to.equal(openpgp.enums.keyStatus.revoked); + await privKey.verifyPrimaryKey(); + await expect(revKey.verifyPrimaryKey()).to.be.rejectedWith('Primary key is revoked'); }); }); @@ -2807,8 +2799,8 @@ describe('Key', function() { expect(revKey.revocationSignatures[0].reasonForRevocationFlag).to.equal(openpgp.enums.reasonForRevocation.key_superseded); expect(revKey.revocationSignatures[0].reasonForRevocationString).to.equal(''); - expect(await subKey.verify(pubKey.primaryKey)).to.equal(openpgp.enums.keyStatus.valid); - expect(await revKey.verify(pubKey.primaryKey)).to.equal(openpgp.enums.keyStatus.revoked); + await subKey.verify(pubKey.primaryKey); + await expect(revKey.verify(pubKey.primaryKey)).to.be.rejectedWith('Subkey is revoked'); }); }); @@ -2922,7 +2914,7 @@ describe('Key', function() { expect(primUser.selfCertification).to.be.an.instanceof(openpgp.packet.Signature); }); - it('getPrimaryUser() should return null if no UserIDs are bound', async function() { + it('getPrimaryUser() should throw if no UserIDs are bound', async function() { const keyWithoutUserID = `-----BEGIN PGP PRIVATE KEY BLOCK----- xVgEWxpFkRYJKwYBBAHaRw8BAQdAYjjZLkp4qG7KAqJeVQlxQT6uCPq6rylV02nC @@ -2936,8 +2928,7 @@ VYGdb3eNlV8CfoEC =FYbP -----END PGP PRIVATE KEY BLOCK-----`; const key = (await openpgp.key.readArmored(keyWithoutUserID)).keys[0]; - const primUser = await key.getPrimaryUser(); - expect(primUser).to.be.null; + await expect(key.getPrimaryUser()).to.be.rejectedWith('Could not find valid self-signature in key 3ce893915c44212f'); }); it('Encrypt - latest created user', async function() { @@ -3025,12 +3016,23 @@ VYGdb3eNlV8CfoEC expect(await key.subKeys[0].getExpirationTime(key.primaryKey)).to.be.null; }); - it('Reject encryption with revoked subkey', async function() { + it('Reject encryption with revoked primary user', async function() { const key = (await openpgp.key.readArmored(pub_revoked_subkeys)).keys[0]; return openpgp.encrypt({publicKeys: [key], message: openpgp.message.fromText('random data')}).then(() => { throw new Error('encryptSessionKey should not encrypt with revoked public key'); }).catch(function(error) { - expect(error.message).to.equal('Error encrypting message: Could not find valid key packet for encryption in key ' + key.getKeyId().toHex()); + expect(error.message).to.equal('Error encrypting message: Primary user is revoked'); + }); + }); + + it('Reject encryption with revoked subkey', async function() { + const key = (await openpgp.key.readArmored(pub_revoked_subkeys)).keys[0]; + key.revocationSignatures = []; + key.users[0].revocationSignatures = []; + return openpgp.encrypt({publicKeys: [key], message: openpgp.message.fromText('random data'), date: new Date(1386842743000)}).then(() => { + throw new Error('encryptSessionKey should not encrypt with revoked public key'); + }).catch(function(error) { + expect(error.message).to.equal('Error encrypting message: Could not find valid encryption key packet in key ' + key.getKeyId().toHex() + ': Subkey is revoked'); }); }); @@ -3039,7 +3041,7 @@ VYGdb3eNlV8CfoEC return openpgp.encrypt({publicKeys: [key], message: openpgp.message.fromText('random data')}).then(() => { throw new Error('encryptSessionKey should not encrypt with revoked public key'); }).catch(function(error) { - expect(error.message).to.equal('Error encrypting message: Could not find valid key packet for encryption in key ' + key.getKeyId().toHex()); + expect(error.message).to.equal('Error encrypting message: Primary key is revoked'); }); }); @@ -3090,7 +3092,7 @@ describe('addSubkey functionality testing', function(){ expect(subkeyN.byteLength()).to.be.equal(rsaBits ? (rsaBits / 8) : pkN.byteLength()); expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('rsa_encrypt_sign'); expect(subKey.getAlgorithmInfo().rsaBits).to.be.equal(rsaBits || privateKey.getAlgorithmInfo().rsaBits); - expect(await subKey.verify(newPrivateKey.primaryKey)).to.be.equal(openpgp.enums.keyStatus.valid); + await subKey.verify(newPrivateKey.primaryKey); }); it('should throw when trying to encrypt a subkey separately from key', async function() { @@ -3113,7 +3115,7 @@ describe('addSubkey functionality testing', function(){ const subKey = importedPrivateKey.subKeys[total]; expect(subKey).to.exist; expect(importedPrivateKey.subKeys.length).to.be.equal(total+1); - expect(await subKey.verify(importedPrivateKey.primaryKey)).to.be.equal(openpgp.enums.keyStatus.valid); + await subKey.verify(importedPrivateKey.primaryKey); }); it('create and add a new ec subkey to a ec key', async function() { @@ -3137,7 +3139,7 @@ describe('addSubkey functionality testing', function(){ const pkOid = privateKey.primaryKey.params[0]; expect(subkeyOid.getName()).to.be.equal(pkOid.getName()); expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('eddsa'); - expect(await subKey.verify(privateKey.primaryKey)).to.be.equal(openpgp.enums.keyStatus.valid); + await subKey.verify(privateKey.primaryKey); }); it('create and add a new ec subkey to a rsa key', async function() { @@ -3153,7 +3155,7 @@ describe('addSubkey functionality testing', function(){ expect(newPrivateKey.subKeys.length).to.be.equal(total+1); expect(subKey.keyPacket.params[0].getName()).to.be.equal(openpgp.enums.curve.curve25519); expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('ecdh'); - expect(await subKey.verify(privateKey.primaryKey)).to.be.equal(openpgp.enums.keyStatus.valid); + await subKey.verify(privateKey.primaryKey); }); it('sign/verify data with the new subkey correctly using curve25519', async function() { @@ -3170,7 +3172,7 @@ describe('addSubkey functionality testing', function(){ const pkOid = newPrivateKey.primaryKey.params[0]; expect(subkeyOid.getName()).to.be.equal(pkOid.getName()); expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('eddsa'); - expect(await subKey.verify(newPrivateKey.primaryKey)).to.be.equal(openpgp.enums.keyStatus.valid); + await subKey.verify(newPrivateKey.primaryKey); expect(await newPrivateKey.getSigningKey()).to.be.equal(subKey); const signed = await openpgp.sign({message: openpgp.cleartext.fromText('the data to signed'), privateKeys: newPrivateKey, armor:false}); const verified = await signed.message.verify([newPrivateKey.toPublic()]); @@ -3191,7 +3193,7 @@ describe('addSubkey functionality testing', function(){ newPrivateKey = (await openpgp.key.readArmored(armoredKey)).keys[0]; const subKey = newPrivateKey.subKeys[total]; const publicKey = newPrivateKey.toPublic(); - expect(await subKey.verify(newPrivateKey.primaryKey)).to.be.equal(openpgp.enums.keyStatus.valid); + await subKey.verify(newPrivateKey.primaryKey); expect(await newPrivateKey.getEncryptionKey()).to.be.equal(subKey); const encrypted = await openpgp.encrypt({message: openpgp.message.fromText(vData), publicKeys: publicKey, armor:false}); expect(encrypted.message).to.be.exist; @@ -3214,7 +3216,7 @@ describe('addSubkey functionality testing', function(){ newPrivateKey = (await openpgp.key.readArmored(armoredKey)).keys[0]; const subKey = newPrivateKey.subKeys[total]; expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('rsa_encrypt_sign'); - expect(await subKey.verify(newPrivateKey.primaryKey)).to.be.equal(openpgp.enums.keyStatus.valid); + await subKey.verify(newPrivateKey.primaryKey); expect(await newPrivateKey.getSigningKey()).to.be.equal(subKey); const signed = await openpgp.sign({message: openpgp.cleartext.fromText('the data to signed'), privateKeys: newPrivateKey, armor:false}); const verified = await signed.message.verify([newPrivateKey.toPublic()]); diff --git a/test/general/openpgp.js b/test/general/openpgp.js index 4475fb6f..89b036f1 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -2388,7 +2388,7 @@ describe('OpenPGP.js public api tests', function() { }).then(function(encrypted) { throw new Error('Should not encrypt with revoked key'); }).catch(function(error) { - expect(error.message).to.match(/Could not find valid key packet for encryption/); + expect(error.message).to.match(/Error encrypting message: Primary key is revoked/); }); }); }); @@ -2405,7 +2405,7 @@ describe('OpenPGP.js public api tests', function() { }).then(function(encrypted) { throw new Error('Should not encrypt with revoked subkey'); }).catch(function(error) { - expect(error.message).to.match(/Could not find valid key packet for encryption/); + expect(error.message).to.match(/Could not find valid encryption key packet/); }); }); }); diff --git a/test/general/signature.js b/test/general/signature.js index d3a97c9a..6e47b40e 100644 --- a/test/general/signature.js +++ b/test/general/signature.js @@ -478,41 +478,52 @@ vwjE8mqJXetNMfj8r2SCyvkEnlVRYR+/mnge+ib56FdJ8uKtqSxyvgA= -----END PGP MESSAGE-----`; it('Testing signature checking on CAST5-enciphered message', async function() { - const priv_key = (await openpgp.key.readArmored(priv_key_arm1)).keys[0]; - const pub_key = (await openpgp.key.readArmored(pub_key_arm1)).keys[0]; - const msg = await openpgp.message.readArmored(msg_arm1); - await priv_key.decrypt("abcd"); - return openpgp.decrypt({ privateKeys: priv_key, publicKeys:[pub_key], message:msg }).then(function(decrypted) { + const { reject_message_hash_algorithms } = openpgp.config; + Object.assign(openpgp.config, { reject_message_hash_algorithms: new Set([openpgp.enums.hash.md5, openpgp.enums.hash.ripemd]) }); + try { + const priv_key = (await openpgp.key.readArmored(priv_key_arm1)).keys[0]; + const pub_key = (await openpgp.key.readArmored(pub_key_arm1)).keys[0]; + const msg = await openpgp.message.readArmored(msg_arm1); + await priv_key.decrypt("abcd"); + const decrypted = await openpgp.decrypt({ privateKeys: priv_key, publicKeys:[pub_key], message:msg }); expect(decrypted.data).to.exist; expect(decrypted.signatures[0].valid).to.be.true; expect(decrypted.signatures[0].signature.packets.length).to.equal(1); - }); + } finally { + Object.assign(openpgp.config, { reject_message_hash_algorithms }); + } }); it('Supports decrypting with GnuPG stripped-key extension', async function() { - // exercises the GnuPG s2k type 1001 extension: - // the secrets on the primary key have been stripped. - const priv_key_gnupg_ext = (await openpgp.key.readArmored(priv_key_arm1_stripped)).keys[0]; - const priv_key_gnupg_ext_2 = (await openpgp.key.readArmored(priv_key_arm1_stripped)).keys[0]; - const pub_key = (await openpgp.key.readArmored(pub_key_arm1)).keys[0]; - const message = await openpgp.message.readArmored(msg_arm1); - const primaryKey_packet = priv_key_gnupg_ext.primaryKey.write(); - expect(priv_key_gnupg_ext.isDecrypted()).to.be.false; - await priv_key_gnupg_ext.decrypt("abcd"); - await priv_key_gnupg_ext_2.decrypt("abcd"); - expect(priv_key_gnupg_ext.isDecrypted()).to.be.true; - const msg = await openpgp.decrypt({ message, privateKeys: [priv_key_gnupg_ext], publicKeys: [pub_key] }); - expect(msg.signatures).to.exist; - expect(msg.signatures).to.have.length(1); - expect(msg.signatures[0].valid).to.be.true; - expect(msg.signatures[0].signature.packets.length).to.equal(1); - await expect(openpgp.sign({ message: openpgp.message.fromText('test'), privateKeys: [priv_key_gnupg_ext] })).to.eventually.be.rejectedWith('Missing private key parameters'); - await expect(openpgp.reformatKey({ userIds: { name: 'test' }, privateKey: priv_key_gnupg_ext })).to.eventually.be.rejectedWith('Missing private key parameters'); - await expect(openpgp.reformatKey({ userIds: { name: 'test' }, privateKey: priv_key_gnupg_ext_2, passphrase: 'test' })).to.eventually.be.rejectedWith('Missing private key parameters'); - await priv_key_gnupg_ext.encrypt("abcd"); - expect(priv_key_gnupg_ext.isDecrypted()).to.be.false; - const primaryKey_packet2 = priv_key_gnupg_ext.primaryKey.write(); - expect(primaryKey_packet).to.deep.equal(primaryKey_packet2); + const { reject_message_hash_algorithms } = openpgp.config; + Object.assign(openpgp.config, { reject_message_hash_algorithms: new Set([openpgp.enums.hash.md5, openpgp.enums.hash.ripemd]) }); + try { + // exercises the GnuPG s2k type 1001 extension: + // the secrets on the primary key have been stripped. + const priv_key_gnupg_ext = (await openpgp.key.readArmored(priv_key_arm1_stripped)).keys[0]; + const priv_key_gnupg_ext_2 = (await openpgp.key.readArmored(priv_key_arm1_stripped)).keys[0]; + const pub_key = (await openpgp.key.readArmored(pub_key_arm1)).keys[0]; + const message = await openpgp.message.readArmored(msg_arm1); + const primaryKey_packet = priv_key_gnupg_ext.primaryKey.write(); + expect(priv_key_gnupg_ext.isDecrypted()).to.be.false; + await priv_key_gnupg_ext.decrypt("abcd"); + await priv_key_gnupg_ext_2.decrypt("abcd"); + expect(priv_key_gnupg_ext.isDecrypted()).to.be.true; + const msg = await openpgp.decrypt({ message, privateKeys: [priv_key_gnupg_ext], publicKeys: [pub_key] }); + expect(msg.signatures).to.exist; + expect(msg.signatures).to.have.length(1); + expect(msg.signatures[0].valid).to.be.true; + expect(msg.signatures[0].signature.packets.length).to.equal(1); + await expect(openpgp.sign({ message: openpgp.message.fromText('test'), privateKeys: [priv_key_gnupg_ext] })).to.eventually.be.rejectedWith('Missing private key parameters'); + await expect(openpgp.reformatKey({ userIds: { name: 'test' }, privateKey: priv_key_gnupg_ext })).to.eventually.be.rejectedWith('Missing private key parameters'); + await expect(openpgp.reformatKey({ userIds: { name: 'test' }, privateKey: priv_key_gnupg_ext_2, passphrase: 'test' })).to.eventually.be.rejectedWith('Missing private key parameters'); + await priv_key_gnupg_ext.encrypt("abcd"); + expect(priv_key_gnupg_ext.isDecrypted()).to.be.false; + const primaryKey_packet2 = priv_key_gnupg_ext.primaryKey.write(); + expect(primaryKey_packet).to.deep.equal(primaryKey_packet2); + } finally { + Object.assign(openpgp.config, { reject_message_hash_algorithms }); + } }); it('Supports signing with GnuPG stripped-key extension', async function() { @@ -523,27 +534,32 @@ vwjE8mqJXetNMfj8r2SCyvkEnlVRYR+/mnge+ib56FdJ8uKtqSxyvgA= }); it('Verify V4 signature. Hash: SHA1. PK: RSA. Signature Type: 0x00 (binary document)', async function() { - const signedArmor = - ['-----BEGIN PGP MESSAGE-----', - 'Version: GnuPG v2.0.19 (GNU/Linux)', - '', - 'owGbwMvMwMT4oOW7S46CznTGNeZJLCWpFSVBU3ZGF2fkF5Uo5KYWFyemp3LlAUUV', - 'cjLzUrneTp3zauvaN9O26L9ZuOFNy4LXyydwcXXMYWFgZGJgY2UCaWXg4hSAmblK', - 'nPmfsXYxd58Ka9eVrEnSpzilr520fXBrJsf2P/oTqzTj3hzyLG0o3TTzxFfrtOXf', - 'cw6U57n3/Z4X0pEZ68C5/o/6NpPICD7fuEOz3936raZ6wXGzueY8pfPnVjY0ajAc', - 'PtJzvvqj+ubYaT1sK9wWhd9lL3/V+9Zuua9QjOWC22buchsCroh8fLoZAA==', - '=VH8F', - '-----END PGP MESSAGE-----'].join('\n'); + const { reject_message_hash_algorithms } = openpgp.config; + Object.assign(openpgp.config, { reject_message_hash_algorithms: new Set([openpgp.enums.hash.md5, openpgp.enums.hash.ripemd]) }); + try { + const signedArmor = + ['-----BEGIN PGP MESSAGE-----', + 'Version: GnuPG v2.0.19 (GNU/Linux)', + '', + 'owGbwMvMwMT4oOW7S46CznTGNeZJLCWpFSVBU3ZGF2fkF5Uo5KYWFyemp3LlAUUV', + 'cjLzUrneTp3zauvaN9O26L9ZuOFNy4LXyydwcXXMYWFgZGJgY2UCaWXg4hSAmblK', + 'nPmfsXYxd58Ka9eVrEnSpzilr520fXBrJsf2P/oTqzTj3hzyLG0o3TTzxFfrtOXf', + 'cw6U57n3/Z4X0pEZ68C5/o/6NpPICD7fuEOz3936raZ6wXGzueY8pfPnVjY0ajAc', + 'PtJzvvqj+ubYaT1sK9wWhd9lL3/V+9Zuua9QjOWC22buchsCroh8fLoZAA==', + '=VH8F', + '-----END PGP MESSAGE-----'].join('\n'); - const sMsg = await openpgp.message.readArmored(signedArmor); - const pub_key = (await openpgp.key.readArmored(pub_key_arm2)).keys[0]; - return sMsg.verify([pub_key]).then(async verified => { + const sMsg = await openpgp.message.readArmored(signedArmor); + const pub_key = (await openpgp.key.readArmored(pub_key_arm2)).keys[0]; + const verified = await sMsg.verify([pub_key]); openpgp.stream.pipe(sMsg.getLiteralData(), new WritableStream()); expect(verified).to.exist; expect(verified).to.have.length(1); expect(await verified[0].verified).to.be.true; expect((await verified[0].signature).packets.length).to.equal(1); - }); + } finally { + Object.assign(openpgp.config, { reject_message_hash_algorithms }); + } }); it('Verify signature of signed and encrypted message from GPG2 with openpgp.decrypt', async function() { @@ -698,8 +714,11 @@ vwjE8mqJXetNMfj8r2SCyvkEnlVRYR+/mnge+ib56FdJ8uKtqSxyvgA= }); it('Verify cleartext signed message with trailing spaces from GPG', async function() { - const msg_armor = - `-----BEGIN PGP SIGNED MESSAGE----- + const { reject_message_hash_algorithms } = openpgp.config; + Object.assign(openpgp.config, { reject_message_hash_algorithms: new Set([openpgp.enums.hash.md5, openpgp.enums.hash.ripemd]) }); + try { + const msg_armor = + `-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 space: @@ -718,21 +737,23 @@ zmuVOdNuWQqxT9Sqa84= =bqAR -----END PGP SIGNATURE-----`; - const plaintext = 'space: \nspace and tab: \t\nno trailing space\n \ntab:\t\ntab and space:\t '; - const csMsg = await openpgp.cleartext.readArmored(msg_armor); - const pubKey = (await openpgp.key.readArmored(pub_key_arm2)).keys[0]; + const plaintext = 'space: \nspace and tab: \t\nno trailing space\n \ntab:\t\ntab and space:\t '; + const csMsg = await openpgp.cleartext.readArmored(msg_armor); + const pubKey = (await openpgp.key.readArmored(pub_key_arm2)).keys[0]; - const keyids = csMsg.getSigningKeyIds(); + const keyids = csMsg.getSigningKeyIds(); - expect(pubKey.getKeys(keyids[0])).to.not.be.empty; + expect(pubKey.getKeys(keyids[0])).to.not.be.empty; - return openpgp.verify({ publicKeys:[pubKey], message:csMsg }).then(function(cleartextSig) { + const cleartextSig = await openpgp.verify({ publicKeys:[pubKey], message:csMsg }); expect(cleartextSig).to.exist; expect(cleartextSig.data).to.equal(plaintext.replace(/[ \t]+$/mg, '')); expect(cleartextSig.signatures).to.have.length(1); expect(cleartextSig.signatures[0].valid).to.be.true; expect(cleartextSig.signatures[0].signature.packets.length).to.equal(1); - }); + } finally { + Object.assign(openpgp.config, { reject_message_hash_algorithms }); + } }); function tests() { @@ -762,7 +783,7 @@ yYDnCgA= expect(cleartextSig).to.exist; expect(openpgp.util.nativeEOL(openpgp.util.Uint8Array_to_str(cleartextSig.data))).to.equal(plaintext); expect(cleartextSig.signatures).to.have.length(1); - expect(cleartextSig.signatures[0].valid).to.be.true; + expect(cleartextSig.signatures[0].valid).to.equal(!openpgp.config.reject_message_hash_algorithms.has(openpgp.enums.hash.sha1)); expect(cleartextSig.signatures[0].signature.packets.length).to.equal(1); }); }); @@ -799,7 +820,11 @@ yYDnCgA= expect(cleartextSig).to.exist; expect(openpgp.util.nativeEOL(openpgp.util.Uint8Array_to_str(await openpgp.stream.readToEnd(cleartextSig.data)))).to.equal(plaintext); expect(cleartextSig.signatures).to.have.length(1); - expect(await cleartextSig.signatures[0].verified).to.be.true; + if (!openpgp.config.reject_message_hash_algorithms.has(openpgp.enums.hash.sha1)) { + expect(await cleartextSig.signatures[0].verified).to.be.true; + } else { + await expect(cleartextSig.signatures[0].verified).to.be.rejectedWith('Insecure message hash algorithm: SHA1'); + } expect((await cleartextSig.signatures[0].signature).packets.length).to.equal(1); }); }); @@ -879,6 +904,18 @@ hkJiXopCSWKSlQInL1devkJJUWJmTmZeugJYlpdLAagQJM0JpsCqIQZwKgAA } }); + let reject_message_hash_algorithms; + tryTests('Accept SHA-1 signatures', tests, { + if: true, + before: function() { + ({ reject_message_hash_algorithms } = openpgp.config); + Object.assign(openpgp.config, { reject_message_hash_algorithms: new Set([openpgp.enums.hash.md5, openpgp.enums.hash.ripemd]) }); + }, + after: function() { + Object.assign(openpgp.config, { reject_message_hash_algorithms }); + } + }); + it('Sign text with openpgp.sign and verify with openpgp.verify leads to same string cleartext and valid signatures', async function() { const plaintext = 'short message\nnext line \n한국어/조선말'; const pubKey = (await openpgp.key.readArmored(pub_key_arm2)).keys[0]; @@ -1234,7 +1271,7 @@ iTuGu4fEU1UligAXSrZmCdE= const key = (await openpgp.key.readArmored(armoredKeyWithPhoto)).keys[0]; for (const user of key.users) { - expect(await user.verify(key.primaryKey)).to.equal(openpgp.enums.keyStatus.valid); + await user.verify(key.primaryKey); } }); diff --git a/test/general/x25519.js b/test/general/x25519.js index 89067084..0aabf317 100644 --- a/test/general/x25519.js +++ b/test/general/x25519.js @@ -425,9 +425,9 @@ const input = require('./testInputs'); expect(user.selfCertifications[0].verify( hi.primaryKey, {userId: user.userId, key: hi.primaryKey} )).to.eventually.be.true; - expect(user.verifyCertificate( + await user.verifyCertificate( hi.primaryKey, user.selfCertifications[0], [hi] - )).to.eventually.equal(openpgp.enums.keyStatus.valid); + ); }); */ }); @@ -460,9 +460,9 @@ function omnibus() { await expect(user.selfCertifications[0].verify( primaryKey, openpgp.enums.signature.cert_generic, { userId: user.userId, key: primaryKey } )).to.eventually.be.true; - await expect(user.verifyCertificate( + await user.verifyCertificate( primaryKey, user.selfCertifications[0], [hi.toPublic()] - )).to.eventually.equal(openpgp.enums.keyStatus.valid); + ); const options = { userIds: { name: "Bye", email: "bye@good.bye" }, @@ -480,9 +480,9 @@ function omnibus() { await expect(user.selfCertifications[0].verify( bye.primaryKey, openpgp.enums.signature.cert_generic, { userId: user.userId, key: bye.primaryKey } )).to.eventually.be.true; - await expect(user.verifyCertificate( + await user.verifyCertificate( bye.primaryKey, user.selfCertifications[0], [bye.toPublic()] - )).to.eventually.equal(openpgp.enums.keyStatus.valid); + ); return Promise.all([ // Hi trusts Bye! diff --git a/test/worker/application_worker.js b/test/worker/application_worker.js index 41764d49..2b1bc425 100644 --- a/test/worker/application_worker.js +++ b/test/worker/application_worker.js @@ -12,7 +12,7 @@ function tests() { it('Should support loading OpenPGP.js from inside a Web Worker', async function() { try { - eval('async function() {}'); + eval('(async function() {})'); } catch (e) { console.error(e); this.skip();