From 1a714cec736797dd70677fe94533131232297f68 Mon Sep 17 00:00:00 2001 From: Mahrud Sayrafi Date: Thu, 4 Jan 2018 01:25:13 -0800 Subject: [PATCH] ECC with async/await is 'Done, without errors.' --- src/cleartext.js | 18 +- src/crypto/crypto.js | 6 +- src/crypto/public_key/elliptic/ecdh.js | 4 +- src/crypto/signature.js | 9 +- src/key.js | 592 +++++++++++------- src/keyring/keyring.js | 4 +- src/message.js | 26 +- src/openpgp.js | 29 +- .../public_key_encrypted_session_key.js | 4 +- src/packet/signature.js | 8 +- test/crypto/crypto.js | 81 ++- test/crypto/elliptic.js | 8 +- test/general/ecc.js | 48 +- test/general/key.js | 230 ++++--- test/general/openpgp.js | 25 +- test/general/packet.js | 71 +-- test/general/signature.js | 132 ++-- 17 files changed, 735 insertions(+), 560 deletions(-) diff --git a/src/cleartext.js b/src/cleartext.js index 94b56eb3..be3bee3b 100644 --- a/src/cleartext.js +++ b/src/cleartext.js @@ -69,8 +69,8 @@ CleartextMessage.prototype.getSigningKeyIds = function() { * @param {Array} privateKeys private keys with decrypted secret key data for signing * @return {module:message~CleartextMessage} new cleartext message with signed content */ -CleartextMessage.prototype.sign = function(privateKeys) { - return new CleartextMessage(this.text, this.signDetached(privateKeys)); +CleartextMessage.prototype.sign = async function(privateKeys) { + return new CleartextMessage(this.text, await this.signDetached(privateKeys)); }; /** @@ -78,25 +78,26 @@ CleartextMessage.prototype.sign = function(privateKeys) { * @param {Array} privateKeys private keys with decrypted secret key data for signing * @return {module:signature~Signature} new detached signature of message content */ -CleartextMessage.prototype.signDetached = function(privateKeys) { +CleartextMessage.prototype.signDetached = async function(privateKeys) { var packetlist = new packet.List(); var literalDataPacket = new packet.Literal(); literalDataPacket.setText(this.text); - for (var i = 0; i < privateKeys.length; i++) { - if (privateKeys[i].isPublic()) { + await Promise.all(privateKeys.map(async function(privateKey) { + if (privateKey.isPublic()) { throw new Error('Need private key for signing'); } var signaturePacket = new packet.Signature(); signaturePacket.signatureType = enums.signature.text; signaturePacket.hashAlgorithm = config.prefer_hash_algorithm; - var signingKeyPacket = privateKeys[i].getSigningKeyPacket(); + var signingKeyPacket = privateKey.getSigningKeyPacket(); signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm; if (!signingKeyPacket.isDecrypted) { throw new Error('Private key is not decrypted.'); } - signaturePacket.sign(signingKeyPacket, literalDataPacket); + await signaturePacket.sign(signingKeyPacket, literalDataPacket); packetlist.push(signaturePacket); - } + })); + return new sigModule.Signature(packetlist); }; @@ -120,6 +121,7 @@ CleartextMessage.prototype.verifyDetached = function(signature, keys) { var literalDataPacket = new packet.Literal(); // we assume that cleartext signature is generated based on UTF8 cleartext literalDataPacket.setText(this.text); + // TODO await Promise.all(signatureList.map(async function(signature) { })); for (var i = 0; i < signatureList.length; i++) { var keyPacket = null; for (var j = 0; j < keys.length; j++) { diff --git a/src/crypto/crypto.js b/src/crypto/crypto.js index d3e3750f..f2eaa1eb 100644 --- a/src/crypto/crypto.js +++ b/src/crypto/crypto.js @@ -74,7 +74,7 @@ export default { */ publicKeyEncrypt: function(algo, publicParams, data, fingerprint) { var types = this.getEncSessionKeyParamTypes(algo); - var result = (function() { + var result = (async function() { var m; switch (algo) { case 'rsa_encrypt': @@ -98,7 +98,9 @@ export default { var curve = publicParams[0]; var kdf_params = publicParams[2]; var R = publicParams[1].toBigInteger(); - var res = ecdh.encrypt(curve.oid, kdf_params.cipher, kdf_params.hash, data, R, fingerprint); + var res = await ecdh.encrypt( + curve.oid, kdf_params.cipher, kdf_params.hash, data, R, fingerprint + ); return constructParams([res.V, res.C], types); default: diff --git a/src/crypto/public_key/elliptic/ecdh.js b/src/crypto/public_key/elliptic/ecdh.js index d43392f2..4419a995 100644 --- a/src/crypto/public_key/elliptic/ecdh.js +++ b/src/crypto/public_key/elliptic/ecdh.js @@ -77,12 +77,12 @@ function kdf(hash_algo, X, length, param) { * @param {String} fingerprint Recipient fingerprint * @return {{V: BigInteger, C: Uint8Array}} Returns ephemeral key and encoded session key */ -function encrypt(oid, cipher_algo, hash_algo, m, Q, fingerprint) { +async function encrypt(oid, cipher_algo, hash_algo, m, Q, fingerprint) { fingerprint = util.hex2Uint8Array(fingerprint); const param = buildEcdhParam(enums.publicKey.ecdh, oid, cipher_algo, hash_algo, fingerprint); const curve = curves.get(oid); cipher_algo = enums.read(enums.symmetric, cipher_algo); - const v = curve.genKeyPair(); + const v = await curve.genKeyPair(); Q = curve.keyFromPublic(Q.toByteArray()); const S = v.derive(Q); const Z = kdf(hash_algo, S, cipher[cipher_algo].keySize, param); diff --git a/src/crypto/signature.js b/src/crypto/signature.js index 18ffd225..284e8779 100644 --- a/src/crypto/signature.js +++ b/src/crypto/signature.js @@ -21,7 +21,7 @@ export default { * @param {Uint8Array} data Data on where the signature was computed on. * @return {Boolean} true if signature (sig_data was equal to data over hash) */ - verify: function(algo, hash_algo, msg_MPIs, publickey_MPIs, data) { + verify: async function(algo, hash_algo, msg_MPIs, publickey_MPIs, data) { var m; data = util.Uint8Array2str(data); @@ -64,7 +64,8 @@ export default { const s = msg_MPIs[1].toBigInteger(); m = data; const Q = publickey_MPIs[1].toBigInteger(); - return ecdsa.verify(curve.oid, hash_algo, {r: r, s: s}, m, Q); + const result = await ecdsa.verify(curve.oid, hash_algo, {r: r, s: s}, m, Q); + return result; default: throw new Error('Invalid signature algorithm.'); } @@ -78,7 +79,7 @@ export default { * @param {Uint8Array} data Data to be signed * @return {Array} */ - sign: function(hash_algo, algo, keyIntegers, data) { + sign: async function(hash_algo, algo, keyIntegers, data) { data = util.Uint8Array2str(data); @@ -120,7 +121,7 @@ export default { var curve = keyIntegers[0]; d = keyIntegers[2].toBigInteger(); m = data; - const signature = ecdsa.sign(curve.oid, hash_algo, m, d); + const signature = await ecdsa.sign(curve.oid, hash_algo, m, d); return util.str2Uint8Array(signature.r.toMPI() + signature.s.toMPI()); default: diff --git a/src/key.js b/src/key.js index b411773b..787f4a83 100644 --- a/src/key.js +++ b/src/key.js @@ -30,6 +30,7 @@ import enums from './enums.js'; import armor from './encoding/armor.js'; import config from './config'; import util from './util'; +import crypto from './crypto'; /** * @class @@ -299,69 +300,81 @@ Key.prototype.armor = function() { */ Key.prototype.getSigningKeyPacket = function(keyId, allowExpired=false) { var primaryUser = this.getPrimaryUser(allowExpired); - if (primaryUser && - isValidSigningKeyPacket(this.primaryKey, primaryUser.selfCertificate) && - (!keyId || this.primaryKey.getKeyId().equals(keyId)) && - this.verifyPrimaryKey(allowExpired) === enums.keyStatus.valid) { + if (primaryUser && (!keyId || this.primaryKey.getKeyId().equals(keyId)) && + isValidSigningKeyPacket(this.primaryKey, primaryUser.selfCertificate, allowExpired)) { return this.primaryKey; } if (this.subKeys) { for (var i = 0; i < this.subKeys.length; i++) { - if (this.subKeys[i].isValidSigningKey(this.primaryKey, allowExpired) && - (!keyId || this.subKeys[i].subKey.getKeyId().equals(keyId))) { - return this.subKeys[i].subKey; + if (!keyId || this.subKeys[i].subKey.getKeyId().equals(keyId)) { + for (var j = 0; j < this.subKeys[i].bindingSignatures.length; j++) { + if (isValidSigningKeyPacket( + this.subKeys[i].subKey, this.subKeys[i].bindingSignatures[j], allowExpired)) { + return this.subKeys[i].subKey; + } + } } } } return null; }; -/** - * Returns preferred signature hash algorithm of this key - * @return {String} - */ -Key.prototype.getPreferredHashAlgorithm = function() { - var primaryUser = this.getPrimaryUser(); - if (primaryUser && primaryUser.selfCertificate.preferredHashAlgorithms) { - return primaryUser.selfCertificate.preferredHashAlgorithms[0]; - } - return config.prefer_hash_algorithm; -}; - -function isValidEncryptionKeyPacket(keyPacket, signature) { +function isValidEncryptionKeyPacket(keyPacket, signature, allowExpired=false) { return keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.dsa) && keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.rsa_sign) && + keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.ecdsa) && + keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.eddsa) && (!signature.keyFlags || (signature.keyFlags[0] & enums.keyFlags.encrypt_communication) !== 0 || - (signature.keyFlags[0] & enums.keyFlags.encrypt_storage) !== 0); + (signature.keyFlags[0] & enums.keyFlags.encrypt_storage) !== 0) && + (allowExpired || (!signature.isExpired() && + // check expiration time of V3 key packet + !(keyPacket.version === 3 && keyPacket.expirationTimeV3 !== 0 && + Date.now() > (keyPacket.created.getTime() + keyPacket.expirationTimeV3*24*3600*1000)) && + // check expiration time of V4 key packet + !(keyPacket.version === 4 && signature.keyNeverExpires === false && + Date.now() > (keyPacket.created.getTime() + signature.keyExpirationTime*1000)))); } -function isValidSigningKeyPacket(keyPacket, signature) { - return (keyPacket.algorithm === enums.read(enums.publicKey, enums.publicKey.dsa) || - keyPacket.algorithm === enums.read(enums.publicKey, enums.publicKey.rsa_sign) || - keyPacket.algorithm === enums.read(enums.publicKey, enums.publicKey.rsa_encrypt_sign) || - keyPacket.algorithm === enums.read(enums.publicKey, enums.publicKey.ecdsa)) && +function isValidSigningKeyPacket(keyPacket, signature, allowExpired=false) { + return keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.rsa_encrypt) && + keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.elgamal) && + keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.ecdh) && (!signature.keyFlags || - (signature.keyFlags[0] & enums.keyFlags.sign_data) !== 0); + (signature.keyFlags[0] & enums.keyFlags.sign_data) !== 0) && + (allowExpired || (!signature.isExpired() && + // check expiration time of V3 key packet + !(keyPacket.version === 3 && keyPacket.expirationTimeV3 !== 0 && + Date.now() > (keyPacket.created.getTime() + keyPacket.expirationTimeV3*24*3600*1000)) && + // check expiration time of V4 key packet + !(keyPacket.version === 4 && signature.keyNeverExpires === false && + Date.now() > (keyPacket.created.getTime() + signature.keyExpirationTime*1000)))); + } /** - * Returns the first valid encryption key packet for this key + * Returns first key packet or key packet by given keyId that is available for encryption or decryption + * @param {module:type/keyid} keyId, optional * @returns {(module:packet/public_subkey|module:packet/secret_subkey|module:packet/secret_key|module:packet/public_key|null)} key packet or null if no encryption key has been found */ -Key.prototype.getEncryptionKeyPacket = function() { +Key.prototype.getEncryptionKeyPacket = function(keyId) { // V4: by convention subkeys are prefered for encryption service // V3: keys MUST NOT have subkeys if (this.subKeys) { for (var i = 0; i < this.subKeys.length; i++) { - if (this.subKeys[i].isValidEncryptionKey(this.primaryKey)) { - return this.subKeys[i].subKey; + if (!keyId || this.subKeys[i].subKey.getKeyId().equals(keyId)) { + for (var j = 0; j < this.subKeys[i].bindingSignatures.length; j++) { + if (isValidEncryptionKeyPacket( + this.subKeys[i].subKey, this.subKeys[i].bindingSignatures[j])) { + return this.subKeys[i].subKey; + } + } } } } // if no valid subkey for encryption, evaluate primary key var primaryUser = this.getPrimaryUser(); - if (primaryUser && primaryUser.selfCertificate && !primaryUser.selfCertificate.isExpired() && + if (primaryUser && (!keyId || this.primaryKey.getKeyId().equals(keyId)) && isValidEncryptionKeyPacket(this.primaryKey, primaryUser.selfCertificate)) { return this.primaryKey; } @@ -436,11 +449,12 @@ Key.prototype.decryptKeyPacket = function(keyIds, passphrase) { * @param {Boolean} allowExpired allows signature verification with expired keys * @return {module:enums.keyStatus} The status of the primary key */ -Key.prototype.verifyPrimaryKey = function(allowExpired=false) { +Key.prototype.verifyPrimaryKey = async function(allowExpired=false) { + // TODO clarify OpenPGP's behavior given an expired revocation signature // check revocation signature if (this.revocationSignature && !this.revocationSignature.isExpired() && (this.revocationSignature.verified || - this.revocationSignature.verify(this.primaryKey, {key: this.primaryKey}))) { + await this.revocationSignature.verify(this.primaryKey, {key: this.primaryKey}))) { return enums.keyStatus.revoked; } // check V3 expiration time @@ -450,17 +464,12 @@ Key.prototype.verifyPrimaryKey = function(allowExpired=false) { } // check for at least one self signature. Self signature of user ID not mandatory // See {@link http://tools.ietf.org/html/rfc4880#section-11.1} - var selfSigned = false; - for (var i = 0; i < this.users.length; i++) { - if (this.users[i].userId && this.users[i].selfCertifications) { - selfSigned = true; - } - } - if (!selfSigned) { + if (!this.users.some(user => user.userId && user.selfCertifications)) { return enums.keyStatus.no_self_cert; } // check for valid self signature - var primaryUser = this.getPrimaryUser(); + await this.verifyPrimaryUser(); + var primaryUser = this.getPrimaryUser(allowExpired); if (!primaryUser) { return enums.keyStatus.invalid; } @@ -510,36 +519,30 @@ function getExpirationTime(keyPacket, selfCertificate) { * @return {{user: Array, selfCertificate: Array}|null} The primary user and the self signature */ Key.prototype.getPrimaryUser = function(allowExpired=false) { - var primUser = []; + var primaryUsers = []; for (var i = 0; i < this.users.length; i++) { + // here we only check the primary user ID, ignoring the primary user attribute if (!this.users[i].userId || !this.users[i].selfCertifications) { continue; } for (var j = 0; j < this.users[i].selfCertifications.length; j++) { - primUser.push({index: i, user: this.users[i], selfCertificate: this.users[i].selfCertifications[j]}); + // only consider already validated certificates + if (!this.users[i].selfCertifications[j].verified || + this.users[i].selfCertifications[j].revoked || + (this.users[i].selfCertifications[j].isExpired() && !allowExpired)) { + continue; + } + primaryUsers.push( + { index: i, user: this.users[i], selfCertificate: this.users[i].selfCertifications[j] } + ); } } // sort by primary user flag and signature creation time - primUser = primUser.sort(function(a, b) { - if (a.selfCertificate.isPrimaryUserID > b.selfCertificate.isPrimaryUserID) { - return -1; - } else if (a.selfCertificate.isPrimaryUserID < b.selfCertificate.isPrimaryUserID) { - return 1; - } else if (a.selfCertificate.created > b.selfCertificate.created) { - return -1; - } else if (a.selfCertificate.created < b.selfCertificate.created) { - return 1; - } else { - return 0; - } + primaryUsers = primaryUsers.sort(function(a, b) { + var A = a.selfCertificate, B = b.selfCertificate; + return A.isPrimaryUserID < B.isPrimaryUserID || A.created < B.created; }); - // return first valid - for (var k = 0; k < primUser.length; k++) { - if (primUser[k].user.isValidSelfCertificate(this.primaryKey, primUser[k].selfCertificate, allowExpired)) { - return primUser[k]; - } - } - return null; + return primaryUsers.pop(); }; /** @@ -550,9 +553,9 @@ Key.prototype.getPrimaryUser = function(allowExpired=false) { * the destination key is tranformed to a private key. * @param {module:key~Key} key source key to merge */ -Key.prototype.update = function(key) { +Key.prototype.update = async function(key) { var that = this; - if (key.verifyPrimaryKey() === enums.keyStatus.invalid) { + if (await key.verifyPrimaryKey() === enums.keyStatus.invalid) { return; } if (this.primaryKey.getFingerprint() !== key.primaryKey.getFingerprint()) { @@ -571,44 +574,45 @@ Key.prototype.update = function(key) { } this.primaryKey = key.primaryKey; } + // TODO clarify OpenPGP's behavior given an expired revocation signature // revocation signature if (!this.revocationSignature && key.revocationSignature && !key.revocationSignature.isExpired() && (key.revocationSignature.verified || - key.revocationSignature.verify(key.primaryKey, {key: key.primaryKey}))) { + await key.revocationSignature.verify(key.primaryKey, {key: key.primaryKey}))) { this.revocationSignature = key.revocationSignature; } // direct signatures - mergeSignatures(key, this, 'directSignatures'); + await mergeSignatures(key, this, 'directSignatures'); + // TODO replace when Promise.some or Promise.any are implemented // users - key.users.forEach(function(srcUser) { + await Promise.all(key.users.map(async function(srcUser) { var found = false; - for (var i = 0; i < that.users.length; i++) { - if ((srcUser.userId && (srcUser.userId.userid === that.users[i].userId.userid)) || - (srcUser.userAttribute && (srcUser.userAttribute.equals(that.users[i].userAttribute)))) { - that.users[i].update(srcUser, that.primaryKey); + await Promise.all(that.users.map(async function(dstUser) { + if ((srcUser.userId && (srcUser.userId.userid === dstUser.userId.userid)) || + (srcUser.userAttribute && (srcUser.userAttribute.equals(dstUser.userAttribute)))) { + await dstUser.update(srcUser, that.primaryKey); found = true; - break; } - } + })); if (!found) { that.users.push(srcUser); } - }); + })); + // TODO replace when Promise.some or Promise.any are implemented // subkeys if (key.subKeys) { - key.subKeys.forEach(function(srcSubKey) { + await Promise.all(key.subKeys.map(async function(srcSubKey) { var found = false; - for (var i = 0; i < that.subKeys.length; i++) { - if (srcSubKey.subKey.getFingerprint() === that.subKeys[i].subKey.getFingerprint()) { - that.subKeys[i].update(srcSubKey, that.primaryKey); + await Promise.all(that.subKeys.map(async function(dstSubKey) { + if (srcSubKey.subKey.getFingerprint() === dstSubKey.subKey.getFingerprint()) { + await dstSubKey.update(srcSubKey, that.primaryKey); found = true; - break; } - } + })); if (!found) { that.subKeys.push(srcSubKey); } - }); + })); } }; @@ -620,20 +624,20 @@ Key.prototype.update = function(key) { * @param {String} attr * @param {Function} checkFn optional, signature only merged if true */ -function mergeSignatures(source, dest, attr, checkFn) { +async function mergeSignatures(source, dest, attr, checkFn) { source = source[attr]; if (source) { if (!dest[attr]) { dest[attr] = source; } else { - source.forEach(function(sourceSig) { - if (!sourceSig.isExpired() && (!checkFn || checkFn(sourceSig)) && + await Promise.all(source.map(async function(sourceSig) { + if (!sourceSig.isExpired() && (!checkFn || await checkFn(sourceSig)) && !dest[attr].some(function(destSig) { return util.equalsUint8Array(destSig.signature,sourceSig.signature); })) { dest[attr].push(sourceSig); } - }); + })); } } } @@ -648,12 +652,13 @@ Key.prototype.revoke = function() { * @param {Array} privateKey decrypted private keys for signing * @return {module:key~Key} new public key with new certificate signature */ -Key.prototype.signPrimaryUser = function(privateKeys) { +Key.prototype.signPrimaryUser = async function(privateKeys) { + await this.verifyPrimaryUser(); var {index, user} = this.getPrimaryUser() || {}; if (!user) { throw new Error('Could not find primary user'); } - user = user.sign(this.primaryKey, privateKeys); + user = await user.sign(this.primaryKey, privateKeys); var key = new Key(this.toPacketlist()); key.users[index] = user; return key; @@ -664,41 +669,77 @@ Key.prototype.signPrimaryUser = function(privateKeys) { * @param {Array} privateKeys decrypted private keys for signing * @return {module:key~Key} new public key with new certificate signature */ -Key.prototype.signAllUsers = function(privateKeys) { - var users = this.users.map(user => user.sign(this.primaryKey, privateKeys)); +Key.prototype.signAllUsers = async function(privateKeys) { + var that = this; var key = new Key(this.toPacketlist()); - key.users = users; + key.users = await Promise.all(this.users.map(function(user) { + return user.sign(that.primaryKey, privateKeys); + })); return key; }; /** * Verifies primary user of key + * - if no arguments are given, verifies the self certificates; + * - otherwise, verifies all certificates signed with given keys. * @param {Array} keys array of keys to verify certificate signatures * @return {Array<({keyid: module:type/keyid, valid: Boolean})>} list of signer's keyid and validity of signature */ -Key.prototype.verifyPrimaryUser = function(keys) { - var {user} = this.getPrimaryUser() || {}; - if (!user) { - throw new Error('Could not find primary user'); - } - return user.verifyAllSignatures(this.primaryKey, keys); +Key.prototype.verifyPrimaryUser = async function(keys) { + var primaryKey = this.primaryKey, primaryUsers = []; + var lastCreated = null, lastPrimaryUserID = null; + await Promise.all(this.users.map(async function(user) { + // here we verify both the primary user ID or the primary user attribute + if (!(user.userId || user.userAttribute) || !user.selfCertifications) { + return; + } + var dataToVerify = { userid: user.userId || user.userAttribute, key: primaryKey }; + await Promise.all(user.selfCertifications.map(async function(selfCertification) { + // skip if certificate is not the most recent + if ((selfCertification.isPrimaryUserID && + selfCertification.isPrimaryUserID < lastPrimaryUserID) || + (!lastPrimaryUserID && selfCertification.created < lastCreated)) { + return; + } + // skip if certificates is not valid + if (!(selfCertification.verified || await selfCertification.verify(primaryKey, dataToVerify)) || + (selfCertification.revoked || await user.isRevoked(primaryKey, selfCertification)) || + selfCertification.isExpired()) { + return; + } + lastPrimaryUserID = selfCertification.isPrimaryUserID; + lastCreated = selfCertification.created; + primaryUsers.push(user); + })); + })); + var user = primaryUsers.pop(); + var results = !user ? [] : keys ? await user.verifyAllCertifications(primaryKey, keys) : + [{ keyid: primaryKey.keyid, valid: await user.verify(primaryKey) === enums.keyStatus.valid }]; + return results; }; /** * Verifies all users of key + * - if no arguments are given, verifies the self certificates; + * - otherwise, verifies all certificates signed with given keys. * @param {Array} keys array of keys to verify certificate signatures * @return {Array<({userid: String, keyid: module:type/keyid, valid: Boolean})>} list of userid, signer's keyid and validity of signature */ -Key.prototype.verifyAllUsers = function(keys) { - return this.users.reduce((signatures, user) => { - return signatures.concat( - user.verifyAllSignatures(this.primaryKey, keys).map(signature => ({ +Key.prototype.verifyAllUsers = async function(keys) { + var results = []; + var primaryKey = this.primaryKey; + await Promise.all(this.users.map(async function(user) { + var signatures = keys ? await user.verifyAllCertifications(primaryKey, keys) : + [{ keyid: primaryKey.keyid, valid: await user.verify(primaryKey) === enums.keyStatus.valid }]; + signatures.forEach(signature => { + results.push({ userid: user.userId.userid, keyid: signature.keyid, valid: signature.valid - })) - ); - }, []); + }); + }); + })); + return results; }; /** @@ -730,102 +771,112 @@ User.prototype.toPacketlist = function() { }; /** - * Checks if a self signature of the user is revoked - * @param {module:packet/signature} certificate + * Checks if a self certificate of the user is revoked * @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet - * @return {Boolean} True if the certificate is revoked + * @param {module:packet/signature} certificate The certificate to verify + * @param {module:packet/public_subkey|module:packet/public_key| + * module:packet/secret_subkey|module:packet/secret_key} key, optional The key to verify the signature + * @return {Boolean} True if the certificate is revoked */ -User.prototype.isRevoked = function(certificate, primaryKey) { +User.prototype.isRevoked = async function(primaryKey, certificate, key) { if (this.revocationCertifications) { - var that = this; - return this.revocationCertifications.some(function(revCert) { + var dataToVerify = { userid: this.userId || this.userAttribute, key: primaryKey }; + // TODO clarify OpenPGP's behavior given an expired revocation signature + var results = await Promise.all(this.revocationCertifications.map(async function(revCert) { return revCert.issuerKeyId.equals(certificate.issuerKeyId) && - !revCert.isExpired() && - (revCert.verified || - revCert.verify(primaryKey, {userid: that.userId || that.userAttribute, key: primaryKey})); - }); + !revCert.isExpired() && + (revCert.verified || revCert.verify(key ? key : primaryKey, dataToVerify)); + })); + certificate.revoked = results.some(result => result === true); + return certificate.revoked; } else { return false; } }; -/** - * Returns true if the self certificate is valid - * @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet - * @param {module:packet/signature} selfCertificate A self certificate of this user - * @param {Boolean} allowExpired allows signature verification with expired keys - * @return {Boolean} - */ -User.prototype.isValidSelfCertificate = function(primaryKey, selfCertificate, allowExpired=false) { - if (this.isRevoked(selfCertificate, primaryKey)) { - return false; - } - if ((!selfCertificate.isExpired() || allowExpired) && - (selfCertificate.verified || - selfCertificate.verify(primaryKey, {userid: this.userId || this.userAttribute, key: primaryKey}))) { - return true; - } - return false; -}; - /** * Signs user * @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet * @param {Array} privateKeys decrypted private keys for signing * @return {module:key~Key} new user with new certificate signatures */ -User.prototype.sign = function(primaryKey, privateKeys) { - var user, dataToSign, signingKeyPacket, signaturePacket; - dataToSign = {}; - dataToSign.key = primaryKey; - dataToSign.userid = this.userId || this.userAttribute; - user = new User(this.userId || this.userAttribute); - user.otherCertifications = []; - privateKeys.forEach(privateKey => { +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(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'); } - signingKeyPacket = privateKey.getSigningKeyPacket(); + await privateKey.verifyPrimaryUser(); + const signingKeyPacket = privateKey.getSigningKeyPacket(); if (!signingKeyPacket) { - throw new Error('Could not find valid signing key packet'); + 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.'); } - signaturePacket = new packet.Signature(); + const signaturePacket = new packet.Signature(); // Most OpenPGP implementations use generic certification (0x10) signaturePacket.signatureType = enums.write(enums.signature, enums.signature.cert_generic); signaturePacket.keyFlags = [enums.keyFlags.certify_keys | enums.keyFlags.sign_data]; - signaturePacket.hashAlgorithm = privateKey.getPreferredHashAlgorithm(); signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm; + signaturePacket.hashAlgorithm = getPreferredHashAlgorithm(privateKey); signaturePacket.signingKeyId = signingKeyPacket.getKeyId(); signaturePacket.sign(signingKeyPacket, dataToSign); - user.otherCertifications.push(signaturePacket); - }); - user.update(this, primaryKey); + return signaturePacket; + })); + await user.update(this, primaryKey); return user; }; /** - * Verifies all user signatures + * Verifies the user certificate + * @param {module:packet/secret_key|module:packet/public_key} 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 {Boolean} allowExpired allows signature verification with expired keys + * @return {module:enums.keyStatus} status of the certificate + */ +User.prototype.verifyCertificate = async function(primaryKey, certificate, keys, allowExpired=false) { + var that = this; + var keyid = certificate.issuerKeyId; + var dataToVerify = { userid: this.userId || this.userAttribute, key: primaryKey }; + var results = await Promise.all(keys.map(async function(key) { + if (!key.getKeyIds().some(id => id.equals(keyid))) { return; } + await key.verifyPrimaryUser(); + var keyPacket = key.getSigningKeyPacket(keyid); + if (certificate.revoked || await that.isRevoked(primaryKey, certificate, keyPacket)) { + return enums.keyStatus.revoked; + } + if (!(certificate.verified || await certificate.verify(keyPacket, dataToVerify))) { + return enums.keyStatus.invalid; + } + if (certificate.isExpired()) { + return enums.keyStatus.expired; + } + return enums.keyStatus.valid; + })); + return results.find(result => result !== undefined); +}; + +/** + * Verifies all user certificates * @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet * @param {Array} keys array of keys to verify certificate signatures * @return {Array<({keyid: module:type/keyid, valid: Boolean})>} list of signer's keyid and validity of signature */ -User.prototype.verifyAllSignatures = function(primaryKey, keys) { - var dataToVerify = { userid: this.userId || this.userAttribute, key: primaryKey }; - var certificates = this.selfCertifications.concat(this.otherCertifications || []); - return certificates.map(signaturePacket => { - var keyPackets = keys.filter(key => key.getSigningKeyPacket(signaturePacket.issuerKeyId)); - var valid = null; - if (keyPackets.length > 0) { - valid = keyPackets.some(keyPacket => signaturePacket.verify(keyPacket.primaryKey, dataToVerify)); - } - return { keyid: signaturePacket.issuerKeyId, valid: valid }; - }); +User.prototype.verifyAllCertifications = async function(primaryKey, keys) { + var that = this; + var certifications = this.selfCertifications.concat(this.otherCertifications || []); + return Promise.all(certifications.map(async function(certification) { + var status = await that.verifyCertificate(primaryKey, certification, keys); + return { keyid: certification.issuerKeyId, + valid: status === undefined ? null : status === enums.keyStatus.valid }; + })); }; /** @@ -834,29 +885,28 @@ User.prototype.verifyAllSignatures = function(primaryKey, keys) { * @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet * @return {module:enums.keyStatus} status of user */ -User.prototype.verify = function(primaryKey) { +User.prototype.verify = async function(primaryKey) { if (!this.selfCertifications) { return enums.keyStatus.no_self_cert; } - var status; - for (var i = 0; i < this.selfCertifications.length; i++) { - if (this.isRevoked(this.selfCertifications[i], primaryKey)) { - status = enums.keyStatus.revoked; - continue; - } - if (!(this.selfCertifications[i].verified || - this.selfCertifications[i].verify(primaryKey, {userid: this.userId || this.userAttribute, key: primaryKey}))) { - status = enums.keyStatus.invalid; - continue; - } - if (this.selfCertifications[i].isExpired()) { - status = enums.keyStatus.expired; - continue; - } - status = enums.keyStatus.valid; - break; - } - return status; + var that = this; + var dataToVerify = { userid: this.userId || this.userAttribute, key: primaryKey }; + // TODO replace when Promise.some or Promise.any are implemented + var results = [enums.keyStatus.invalid].concat( + await Promise.all(this.selfCertifications.map(async function(selfCertification, i) { + if (selfCertification.revoked || await that.isRevoked(primaryKey, selfCertification)) { + return enums.keyStatus.revoked; + } + if (!(selfCertification.verified || await selfCertification.verify(primaryKey, dataToVerify))) { + return enums.keyStatus.invalid; + } + if (selfCertification.isExpired()) { + return enums.keyStatus.expired; + } + return enums.keyStatus.valid; + }))); + return results.some(status => status === enums.keyStatus.valid)? + enums.keyStatus.valid : results.pop(); }; /** @@ -864,17 +914,16 @@ User.prototype.verify = function(primaryKey) { * @param {module:key~User} user source user to merge * @param {module:packet/signature} primaryKey primary key used for validation */ -User.prototype.update = function(user, primaryKey) { - var that = this; +User.prototype.update = async function(user, primaryKey) { + var dataToVerify = { userid: this.userId || this.userAttribute, key: primaryKey }; // self signatures - mergeSignatures(user, this, 'selfCertifications', function(srcSelfSig) { - return srcSelfSig.verified || - srcSelfSig.verify(primaryKey, {userid: that.userId || that.userAttribute, key: primaryKey}); + await mergeSignatures(user, this, 'selfCertifications', async function(srcSelfSig) { + return srcSelfSig.verified || srcSelfSig.verify(primaryKey, dataToVerify); }); // other signatures - mergeSignatures(user, this, 'otherCertifications'); + await mergeSignatures(user, this, 'otherCertifications'); // revocation signatures - mergeSignatures(user, this, 'revocationCertifications'); + await mergeSignatures(user, this, 'revocationCertifications'); }; /** @@ -909,8 +958,8 @@ SubKey.prototype.toPacketlist = function() { * @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet * @return {Boolean} */ -SubKey.prototype.isValidEncryptionKey = function(primaryKey) { - if(this.verify(primaryKey) !== enums.keyStatus.valid) { +SubKey.prototype.isValidEncryptionKey = async function(primaryKey) { + if(await this.verify(primaryKey) !== enums.keyStatus.valid) { return false; } for(var i = 0; i < this.bindingSignatures.length; i++) { @@ -927,12 +976,12 @@ SubKey.prototype.isValidEncryptionKey = function(primaryKey) { * @param {Boolean} allowExpired allows signature verification with expired keys * @return {Boolean} */ -SubKey.prototype.isValidSigningKey = function(primaryKey, allowExpired=false) { - if(this.verify(primaryKey, allowExpired) !== enums.keyStatus.valid) { +SubKey.prototype.isValidSigningKey = async function(primaryKey, allowExpired=false) { + if(await this.verify(primaryKey, allowExpired) !== enums.keyStatus.valid) { return false; } for(var i = 0; i < this.bindingSignatures.length; i++) { - if(isValidSigningKeyPacket(this.subKey, this.bindingSignatures[i])) { + if(isValidSigningKeyPacket(this.subKey, this.bindingSignatures[i], allowExpired)) { return true; } } @@ -946,11 +995,13 @@ SubKey.prototype.isValidSigningKey = function(primaryKey, allowExpired=false) { * @param {Boolean} allowExpired allows signature verification with expired keys * @return {module:enums.keyStatus} The status of the subkey */ -SubKey.prototype.verify = function(primaryKey, allowExpired=false) { +SubKey.prototype.verify = async function(primaryKey, allowExpired=false) { + var that = this; + // TODO clarify OpenPGP's behavior given an expired revocation signature // check subkey revocation signature if (this.revocationSignature && !this.revocationSignature.isExpired() && (this.revocationSignature.verified || - this.revocationSignature.verify(primaryKey, {key:primaryKey, bind: this.subKey}))) { + await this.revocationSignature.verify(primaryKey, {key:primaryKey, bind: this.subKey}))) { return enums.keyStatus.revoked; } // check V3 expiration time @@ -959,38 +1010,29 @@ SubKey.prototype.verify = function(primaryKey, allowExpired=false) { return enums.keyStatus.expired; } // check subkey binding signatures (at least one valid binding sig needed) - for(var i = 0; i < this.bindingSignatures.length; i++) { - var isLast = (i === this.bindingSignatures.length - 1); - var sig = this.bindingSignatures[i]; + // TODO replace when Promise.some or Promise.any are implemented + var results = [enums.keyStatus.invalid].concat( + await Promise.all(this.bindingSignatures.map(async function(bindingSignature) { // check binding signature is not expired - if(!allowExpired && sig.isExpired()) { - if(isLast) { - return enums.keyStatus.expired; // last expired binding signature - } else { - continue; - } + if(!allowExpired && bindingSignature.isExpired()) { + return enums.keyStatus.expired; // last expired binding signature } // check binding signature can verify - if (!(sig.verified || sig.verify(primaryKey, {key: primaryKey, bind: this.subKey}))) { - if(isLast) { - return enums.keyStatus.invalid; // last invalid binding signature - } else { - continue; - } + if (!(bindingSignature.verified || + await bindingSignature.verify(primaryKey, {key: primaryKey, bind: that.subKey}))) { + return enums.keyStatus.invalid; // last invalid binding signature } // check V4 expiration time - if (this.subKey.version === 4) { - if(!allowExpired && sig.keyNeverExpires === false && Date.now() > (this.subKey.created.getTime() + sig.keyExpirationTime*1000)) { - if(isLast) { - return enums.keyStatus.expired; // last V4 expired binding signature - } else { - continue; - } + if (that.subKey.version === 4) { + if(!allowExpired && bindingSignature.keyNeverExpires === false && + Date.now() > (that.subKey.created.getTime() + bindingSignature.keyExpirationTime*1000)) { + return enums.keyStatus.expired; // last V4 expired binding signature } } return enums.keyStatus.valid; // found a binding signature that passed all checks - } - return enums.keyStatus.invalid; // no binding signatures to check + }))); + return results.some(status => status === enums.keyStatus.valid) ? + enums.keyStatus.valid : results.pop(); }; /** @@ -1016,8 +1058,8 @@ SubKey.prototype.getExpirationTime = function() { * @param {module:key~SubKey} subKey source subkey to merge * @param {module:packet/signature} primaryKey primary key used for validation */ -SubKey.prototype.update = function(subKey, primaryKey) { - if (subKey.verify(primaryKey) === enums.keyStatus.invalid) { +SubKey.prototype.update = async function(subKey, primaryKey) { + if (await subKey.verify(primaryKey) === enums.keyStatus.invalid) { return; } if (this.subKey.getFingerprint() !== subKey.subKey.getFingerprint()) { @@ -1029,18 +1071,28 @@ SubKey.prototype.update = function(subKey, primaryKey) { this.subKey = subKey.subKey; } // update missing binding signatures - if(this.bindingSignatures.length < subKey.bindingSignatures.length) { - for(var i = this.bindingSignatures.length; i < subKey.bindingSignatures.length; i++) { - var newSig = subKey.bindingSignatures[i]; - if (newSig.verified || newSig.verify(primaryKey, {key: primaryKey, bind: this.subKey})) { - this.bindingSignatures.push(newSig); + var that = this; + await Promise.all(subKey.bindingSignatures.map(async function(newBindingSignature) { + if (newBindingSignature.verified || + await newBindingSignature.verify(primaryKey, {key: primaryKey, bind: that.subKey })) { + for (var i = 0; i < that.bindingSignatures.length; i++) { + if (that.bindingSignatures[i].issuerKeyId.equals(newBindingSignature.issuerKeyId)) { + that.bindingSignatures[i] = newBindingSignature; + return; + } } + that.bindingSignatures.push(newBindingSignature); } - } + })); + // TODO clarify OpenPGP's behavior given an expired revocation signature // revocation signature - if (!this.revocationSignature && subKey.revocationSignature && !subKey.revocationSignature.isExpired() && - (subKey.revocationSignature.verified || - subKey.revocationSignature.verify(primaryKey, {key: primaryKey, bind: this.subKey}))) { + if (!this.revocationSignature && + subKey.revocationSignature && + !subKey.revocationSignature.isExpired() && + (subKey.revocationSignature.verified || + await subKey.revocationSignature.verify( + primaryKey, {key: primaryKey, bind: this.subKey} + ))) { this.revocationSignature = subKey.revocationSignature; } }; @@ -1099,7 +1151,7 @@ export function readArmored(armoredText) { } /** - * Generates a new OpenPGP key. Currently only supports RSA keys. + * Generates a new OpenPGP key. Supports RSA and ECC keys. * Primary and subkey will be of same type. * @param {module:enums.publicKey} [options.keyType=module:enums.publicKey.rsa_encrypt_sign] to indicate what type of key to make. * RSA is 1. See {@link http://tools.ietf.org/html/rfc4880#section-9.1} @@ -1117,20 +1169,34 @@ export function generate(options) { return Promise.resolve().then(() => { if (options.curve) { - options.keyType = options.keyType || enums.publicKey.ecdsa; + try { + options.curve = enums.write(enums.curve, options.curve); + } catch (e) { + throw new Error('Not valid curve.') + } + if (options.curve === enums.curve.ed25519 || options.curve === enums.curve.curve25519) { + options.keyType = options.keyType || enums.publicKey.eddsa; + } else { + options.keyType = options.keyType || enums.publicKey.ecdsa; + } options.subkeyType = options.subkeyType || enums.publicKey.ecdh; - } else { + } else if (options.numBits) { options.keyType = options.keyType || enums.publicKey.rsa_encrypt_sign; options.subkeyType = options.subkeyType || enums.publicKey.rsa_encrypt_sign; + } else { + throw new Error('Key type not specified.'); } if (options.keyType !== enums.publicKey.rsa_encrypt_sign && - options.keyType !== enums.publicKey.ecdsa) { // RSA Encrypt-Only and RSA Sign-Only are deprecated and SHOULD NOT be generated + options.keyType !== enums.publicKey.ecdsa && + options.keyType !== enums.publicKey.eddsa) { + // RSA Encrypt-Only and RSA Sign-Only are deprecated and SHOULD NOT be generated throw new Error('Unsupported key type'); } if (options.subkeyType !== enums.publicKey.rsa_encrypt_sign && - options.subkeyType !== enums.publicKey.ecdh) { // RSA Encrypt-Only and RSA Sign-Only are deprecated and SHOULD NOT be generated + options.subkeyType !== enums.publicKey.ecdh) { + // RSA Encrypt-Only and RSA Sign-Only are deprecated and SHOULD NOT be generated throw new Error('Unsupported subkey type'); } @@ -1148,13 +1214,17 @@ export function generate(options) { function generateSecretKey() { secretKeyPacket = new packet.SecretKey(); + secretKeyPacket.packets = null; secretKeyPacket.algorithm = enums.read(enums.publicKey, options.keyType); + options.curve = options.curve === enums.curve.curve25519 ? enums.curve.ed25519 : options.curve; return secretKeyPacket.generate(options.numBits, options.curve); } function generateSecretSubkey() { secretSubkeyPacket = new packet.SecretSubkey(); + secretKeyPacket.packets = null; secretSubkeyPacket.algorithm = enums.read(enums.publicKey, options.subkeyType); + options.curve = options.curve === enums.curve.ed25519 ? enums.curve.curve25519 : options.curve; return secretSubkeyPacket.generate(options.numBits, options.curve); } } @@ -1204,7 +1274,7 @@ export function reformat(options) { }); } -function wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options) { +async function wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options) { // set passphrase protection if (options.passphrase) { secretKeyPacket.encrypt(options.passphrase); @@ -1215,7 +1285,7 @@ function wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options) { packetlist.push(secretKeyPacket); - options.userIds.forEach(function(userId, index) { + await Promise.all(options.userIds.map(async function(userId, index) { var userIdPacket = new packet.Userid(); userIdPacket.read(util.str2Uint8Array(userId)); @@ -1226,7 +1296,7 @@ function wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options) { var signaturePacket = new packet.Signature(); signaturePacket.signatureType = enums.signature.cert_generic; signaturePacket.publicKeyAlgorithm = options.keyType; - signaturePacket.hashAlgorithm = config.prefer_hash_algorithm; + signaturePacket.hashAlgorithm = getPreferredHashAlgorithm(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) @@ -1254,11 +1324,14 @@ function wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options) { signaturePacket.keyExpirationTime = options.keyExpirationTime; signaturePacket.keyNeverExpires = false; } - signaturePacket.sign(secretKeyPacket, dataToSign); - - packetlist.push(userIdPacket); - packetlist.push(signaturePacket); + await signaturePacket.sign(secretKeyPacket, dataToSign); + return {userIdPacket, signaturePacket}; + })).then(list => { + list.forEach(({userIdPacket, signaturePacket}) => { + packetlist.push(userIdPacket); + packetlist.push(signaturePacket); + }); }); var dataToSign = {}; @@ -1267,13 +1340,13 @@ function wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options) { var subkeySignaturePacket = new packet.Signature(); subkeySignaturePacket.signatureType = enums.signature.subkey_binding; subkeySignaturePacket.publicKeyAlgorithm = options.keyType; - subkeySignaturePacket.hashAlgorithm = config.prefer_hash_algorithm; + subkeySignaturePacket.hashAlgorithm = getPreferredHashAlgorithm(secretSubkeyPacket); subkeySignaturePacket.keyFlags = [enums.keyFlags.encrypt_communication | enums.keyFlags.encrypt_storage]; if (options.keyExpirationTime > 0) { subkeySignaturePacket.keyExpirationTime = options.keyExpirationTime; subkeySignaturePacket.keyNeverExpires = false; } - subkeySignaturePacket.sign(secretKeyPacket, dataToSign); + await subkeySignaturePacket.sign(secretKeyPacket, dataToSign); packetlist.push(secretSubkeyPacket); packetlist.push(subkeySignaturePacket); @@ -1286,6 +1359,39 @@ function wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options) { return new Key(packetlist); } +/** + * Returns the preferred signature hash algorithm of a key + * @param {object} key + * @return {String} + */ +export function getPreferredHashAlgorithm(key) { + var hash_algo = config.prefer_hash_algorithm, + pref_algo = hash_algo; + if (Key.prototype.isPrototypeOf(key)) { + var primaryUser = key.getPrimaryUser(); + if (primaryUser && primaryUser.selfCertificate.preferredHashAlgorithms) { + pref_algo = primaryUser.selfCertificate.preferredHashAlgorithms[0]; + hash_algo = crypto.hash.getHashByteLength(hash_algo) <= crypto.hash.getHashByteLength(pref_algo) ? + pref_algo : hash_algo; + } + key = key.getSigningKeyPacket(); + } + switch(Object.getPrototypeOf(key)) { + case packet.SecretKey.prototype: + case packet.PublicKey.prototype: + case packet.SecretSubkey.prototype: + case packet.PublicSubkey.prototype: + switch(key.algorithm) { + case 'ecdh': + case 'ecdsa': + case 'eddsa': + pref_algo = crypto.publicKey.elliptic.getPreferredHashAlgorithm(key.params[0]); + } + } + return crypto.hash.getHashByteLength(hash_algo) <= crypto.hash.getHashByteLength(pref_algo) ? + pref_algo : hash_algo; +} + /** * Returns the preferred symmetric algorithm for a set of keys * @param {Array} keys Set of keys diff --git a/src/keyring/keyring.js b/src/keyring/keyring.js index 97e8975f..11fd8d5e 100644 --- a/src/keyring/keyring.js +++ b/src/keyring/keyring.js @@ -183,12 +183,12 @@ KeyArray.prototype.getForId = function (keyId, deep) { KeyArray.prototype.importKey = function (armored) { var imported = keyModule.readArmored(armored); var that = this; - imported.keys.forEach(function(key) { + imported.keys.forEach(async function(key) { // check if key already in key array var keyidHex = key.primaryKey.getKeyId().toHex(); var keyFound = that.getForId(keyidHex); if (keyFound) { - keyFound.update(key); + await keyFound.update(key); } else { that.push(key); } diff --git a/src/message.js b/src/message.js index 87f01b52..bcf31282 100644 --- a/src/message.js +++ b/src/message.js @@ -312,7 +312,7 @@ export function encryptSessionKey(sessionKey, symAlgo, publicKeys, passwords) { * @param {Signature} signature (optional) any existing detached signature to add to the message * @return {module:message~Message} new message with signed content */ -Message.prototype.sign = function(privateKeys=[], signature=null) { +Message.prototype.sign = async function(privateKeys=[], signature=null) { var packetlist = new packet.List(); @@ -349,7 +349,7 @@ Message.prototype.sign = function(privateKeys=[], signature=null) { } onePassSig = new packet.OnePassSignature(); onePassSig.type = signatureType; - //TODO get preferred hashg algo from key signature + //TODO get preferred hash algo from key signature onePassSig.hashAlgorithm = config.prefer_hash_algorithm; signingKeyPacket = privateKeys[i].getSigningKeyPacket(); if (!signingKeyPacket) { @@ -365,17 +365,20 @@ Message.prototype.sign = function(privateKeys=[], signature=null) { packetlist.push(literalDataPacket); - for (i = privateKeys.length - 1; i >= 0; i--) { + // FIXME does the order matter here? It used to be n-1..0 + await Promise.all(privateKeys.map(async function(privateKey) { + var signingKeyPacket = privateKey.getSigningKeyPacket(); var signaturePacket = new packet.Signature(); signaturePacket.signatureType = signatureType; signaturePacket.hashAlgorithm = config.prefer_hash_algorithm; + // FIXME FIXME were we just signing with the last key? signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm; if (!signingKeyPacket.isDecrypted) { throw new Error('Private key is not decrypted.'); } - signaturePacket.sign(signingKeyPacket, literalDataPacket); + await signaturePacket.sign(signingKeyPacket, literalDataPacket); packetlist.push(signaturePacket); - } + })); if (signature) { packetlist.concat(existingSigPacketlist); @@ -390,7 +393,7 @@ Message.prototype.sign = function(privateKeys=[], signature=null) { * @param {Signature} signature (optional) any existing detached signature * @return {module:signature~Signature} new detached signature of message content */ -Message.prototype.signDetached = function(privateKeys=[], signature=null) { +Message.prototype.signDetached = async function(privateKeys=[], signature=null) { var packetlist = new packet.List(); @@ -403,8 +406,8 @@ Message.prototype.signDetached = function(privateKeys=[], signature=null) { var signatureType = literalFormat === enums.literal.binary ? enums.signature.binary : enums.signature.text; - for (var i = 0; i < privateKeys.length; i++) { - var signingKeyPacket = privateKeys[i].getSigningKeyPacket(); + await Promise.all(privateKeys.map(async function(privateKey) { + var signingKeyPacket = privateKey.getSigningKeyPacket(); var signaturePacket = new packet.Signature(); signaturePacket.signatureType = signatureType; signaturePacket.hashAlgorithm = config.prefer_hash_algorithm; @@ -412,9 +415,10 @@ Message.prototype.signDetached = function(privateKeys=[], signature=null) { if (!signingKeyPacket.isDecrypted) { throw new Error('Private key is not decrypted.'); } - signaturePacket.sign(signingKeyPacket, literalDataPacket); + await signaturePacket.sign(signingKeyPacket, literalDataPacket); packetlist.push(signaturePacket); - } + })); + if (signature) { var existingSigPacketlist = signature.packets.filterByTag(enums.packet.signature); packetlist.concat(existingSigPacketlist); @@ -489,7 +493,7 @@ function createVerificationObjects(signatureList, literalDataList, keys) { result.push(verifiedSig); } - return result; + return Promise.all(result); } /** diff --git a/src/openpgp.js b/src/openpgp.js index c28228ac..fd3394f1 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -201,7 +201,7 @@ export function encrypt({ data, publicKeys, privateKeys, passwords, sessionKey, return asyncProxy.delegate('encrypt', { data, publicKeys, privateKeys, passwords, sessionKey, filename, armor, detached, signature, returnSessionKey }); } var result = {}; - return Promise.resolve().then(() => { + return Promise.resolve().then(async function() { let message = createMessage(data, filename); if (!privateKeys) { @@ -209,14 +209,14 @@ export function encrypt({ data, publicKeys, privateKeys, passwords, sessionKey, } if (privateKeys.length || signature) { // sign the message only if private keys or signature is specified if (detached) { - var detachedSignature = message.signDetached(privateKeys, signature); + var detachedSignature = await message.signDetached(privateKeys, signature); if (armor) { result.signature = detachedSignature.armor(); } else { result.signature = detachedSignature; } } else { - message = message.sign(privateKeys, signature); + message = await message.sign(privateKeys, signature); } } return message.encrypt(publicKeys, passwords, sessionKey); @@ -231,6 +231,7 @@ export function encrypt({ data, publicKeys, privateKeys, passwords, sessionKey, result.sessionKey = encrypted.sessionKey; } return result; + }).catch(onError.bind(null, 'Error encrypting message')); } @@ -255,7 +256,7 @@ export function decrypt({ message, privateKey, publicKeys, sessionKey, password, return asyncProxy.delegate('decrypt', { message, privateKey, publicKeys, sessionKey, password, format, signature }); } - return message.decrypt(privateKey, sessionKey, password).then(message => { + return message.decrypt(privateKey, sessionKey, password).then(async function(message) { const result = parseMessage(message, format); @@ -264,9 +265,9 @@ export function decrypt({ message, privateKey, publicKeys, sessionKey, password, } if (signature) { //detached signature - result.signatures = message.verifyDetached(signature, publicKeys); + result.signatures = await message.verifyDetached(signature, publicKeys); } else { - result.signatures = message.verify(publicKeys); + result.signatures = await message.verify(publicKeys); } return result; @@ -302,7 +303,7 @@ export function sign({ data, privateKeys, armor=true, detached=false}) { } var result = {}; - const promise = async function(){ + return Promise.resolve().then(async function() { var message; if (util.isString(data)) { @@ -326,10 +327,9 @@ export function sign({ data, privateKeys, armor=true, detached=false}) { result.message = message; } } - return result; - } - return promise().catch(onError.bind(null, 'Error signing cleartext message')); + + }).catch(onError.bind(null, 'Error signing cleartext message')); } /** @@ -349,13 +349,15 @@ export function verify({ message, publicKeys, signature=null }) { return asyncProxy.delegate('verify', { message, publicKeys, signature }); } - var result = {}; - const promise = async function(){ + return Promise.resolve().then(async function() { + + var result = {}; if (cleartext.CleartextMessage.prototype.isPrototypeOf(message)) { result.data = message.getText(); } else { result.data = message.getLiteralData(); } + if (signature) { //detached signature result.signatures = await message.verifyDetached(signature, publicKeys); @@ -364,8 +366,7 @@ export function verify({ message, publicKeys, signature=null }) { } return result; - } - return promise().catch(onError.bind(null, 'Error verifying cleartext signed message')); + }).catch(onError.bind(null, 'Error verifying cleartext signed message')); } diff --git a/src/packet/public_key_encrypted_session_key.js b/src/packet/public_key_encrypted_session_key.js index 925abd3d..4785a6c9 100644 --- a/src/packet/public_key_encrypted_session_key.js +++ b/src/packet/public_key_encrypted_session_key.js @@ -102,7 +102,7 @@ PublicKeyEncryptedSessionKey.prototype.write = function () { return util.concatUint8Array(arr); }; -PublicKeyEncryptedSessionKey.prototype.encrypt = function (key) { +PublicKeyEncryptedSessionKey.prototype.encrypt = async function (key) { var data = String.fromCharCode( enums.write(enums.symmetric, this.sessionKeyAlgorithm)); @@ -117,7 +117,7 @@ PublicKeyEncryptedSessionKey.prototype.encrypt = function (key) { toEncrypt = new type_mpi(crypto.pkcs1.eme.encode(data, key.params[0].byteLength())); } - this.encrypted = crypto.publicKeyEncrypt( + this.encrypted = await crypto.publicKeyEncrypt( this.publicKeyAlgorithm, key.params, toEncrypt, diff --git a/src/packet/signature.js b/src/packet/signature.js index c818b900..cce0fd5c 100644 --- a/src/packet/signature.js +++ b/src/packet/signature.js @@ -211,7 +211,7 @@ Signature.prototype.write = function () { * @param {module:packet/secret_key} key private key used to sign the message. * @param {Object} data Contains packets to be signed. */ -Signature.prototype.sign = function (key, data) { +Signature.prototype.sign = async function (key, data) { var signatureType = enums.write(enums.signature, this.signatureType), publicKeyAlgorithm = enums.write(enums.publicKey, this.publicKeyAlgorithm), hashAlgorithm = enums.write(enums.hash, this.hashAlgorithm); @@ -243,7 +243,7 @@ Signature.prototype.sign = function (key, data) { this.signedHashValue = hash.subarray(0, 2); - this.signature = crypto.signature.sign(hashAlgorithm, + this.signature = await crypto.signature.sign(hashAlgorithm, publicKeyAlgorithm, key.params, toHash); }; @@ -615,7 +615,7 @@ Signature.prototype.calculateTrailer = function () { * module:packet/secret_subkey|module:packet/secret_key} key the public key to verify the signature * @return {boolean} True if message is verified, else false. */ -Signature.prototype.verify = function (key, data) { +Signature.prototype.verify = async function (key, data) { var signatureType = enums.write(enums.signature, this.signatureType), publicKeyAlgorithm = enums.write(enums.publicKey, this.publicKeyAlgorithm), hashAlgorithm = enums.write(enums.hash, this.hashAlgorithm); @@ -644,7 +644,7 @@ Signature.prototype.verify = function (key, data) { i += mpi[j].read(this.signature.subarray(i, this.signature.length)); } - this.verified = crypto.signature.verify(publicKeyAlgorithm, + this.verified = await crypto.signature.verify(publicKeyAlgorithm, hashAlgorithm, mpi, key.params, util.concatUint8Array([bytes, this.signatureData, trailer])); diff --git a/test/crypto/crypto.js b/test/crypto/crypto.js index c4dda83f..e6c7d8c2 100644 --- a/test/crypto/crypto.js +++ b/test/crypto/crypto.js @@ -2,8 +2,9 @@ var openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); -var chai = require('chai'), - expect = chai.expect; +var chai = require('chai'); +chai.use(require('chai-as-promised')); +var expect = chai.expect; describe('API functional testing', function() { var util = openpgp.util; @@ -227,29 +228,39 @@ describe('API functional testing', function() { var data = util.str2Uint8Array("foobar"); describe('Sign and verify', function () { - it('RSA', function (done) { + it('RSA', function () { //Originally we passed public and secret MPI separately, now they are joined. Is this what we want to do long term? // RSA - var RSAsignedData = openpgp.crypto.signature.sign(2, 1, RSApubMPIs.concat(RSAsecMPIs), data); - var RSAsignedDataMPI = new openpgp.MPI(); - RSAsignedDataMPI.read(RSAsignedData); - var success = openpgp.crypto.signature.verify(1, 2, [RSAsignedDataMPI], RSApubMPIs, data); - expect(success).to.be.true; - done(); + return openpgp.crypto.signature.sign( + 2, 1, RSApubMPIs.concat(RSAsecMPIs), data + ).then(RSAsignedData => { + var RSAsignedDataMPI = new openpgp.MPI(); + RSAsignedDataMPI.read(RSAsignedData); + return openpgp.crypto.signature.verify( + 1, 2, [RSAsignedDataMPI], RSApubMPIs, data + ).then(success => { + return expect(success).to.be.true; + }); + }); }); - it('DSA', function (done) { + it('DSA', function () { // DSA - var DSAsignedData = util.Uint8Array2str(openpgp.crypto.signature.sign(2, 17, DSApubMPIs.concat(DSAsecMPIs), data)); - - var DSAmsgMPIs = []; - DSAmsgMPIs[0] = new openpgp.MPI(); - DSAmsgMPIs[1] = new openpgp.MPI(); - DSAmsgMPIs[0].read(DSAsignedData.substring(0,34)); - DSAmsgMPIs[1].read(DSAsignedData.substring(34,68)); - var success = openpgp.crypto.signature.verify(17, 2, DSAmsgMPIs, DSApubMPIs, data); - expect(success).to.be.true; - done(); + return openpgp.crypto.signature.sign( + 2, 17, DSApubMPIs.concat(DSAsecMPIs), data + ).then(DSAsignedData => { + var DSAsignedData = util.Uint8Array2str(DSAsignedData); + var DSAmsgMPIs = []; + DSAmsgMPIs[0] = new openpgp.MPI(); + DSAmsgMPIs[1] = new openpgp.MPI(); + DSAmsgMPIs[0].read(DSAsignedData.substring(0,34)); + DSAmsgMPIs[1].read(DSAsignedData.substring(34,68)); + return openpgp.crypto.signature.verify( + 17, 2, DSAmsgMPIs, DSApubMPIs, data + ).then(success => { + return expect(success).to.be.true; + }); + }); }); }); @@ -357,28 +368,34 @@ describe('API functional testing', function() { var symmKey = util.Uint8Array2str(openpgp.crypto.generateSessionKey('aes256')); var RSAUnencryptedData = new openpgp.MPI(); RSAUnencryptedData.fromBytes(openpgp.crypto.pkcs1.eme.encode(symmKey, RSApubMPIs[0].byteLength())); - var RSAEncryptedData = openpgp.crypto.publicKeyEncrypt("rsa_encrypt_sign", RSApubMPIs, RSAUnencryptedData); + openpgp.crypto.publicKeyEncrypt( + "rsa_encrypt_sign", RSApubMPIs, RSAUnencryptedData + ).then(RSAEncryptedData => { - var data = openpgp.crypto.publicKeyDecrypt("rsa_encrypt_sign", RSApubMPIs.concat(RSAsecMPIs), RSAEncryptedData).write(); - data = util.Uint8Array2str(data.subarray(2, data.length)); + var data = openpgp.crypto.publicKeyDecrypt("rsa_encrypt_sign", RSApubMPIs.concat(RSAsecMPIs), RSAEncryptedData).write(); + data = util.Uint8Array2str(data.subarray(2, data.length)); - var result = openpgp.crypto.pkcs1.eme.decode(data, RSApubMPIs[0].byteLength()); - expect(result).to.equal(symmKey); - done(); + var result = openpgp.crypto.pkcs1.eme.decode(data, RSApubMPIs[0].byteLength()); + expect(result).to.equal(symmKey); + done(); + }); }); it('Asymmetric using Elgamal with eme_pkcs1 padding', function (done) { var symmKey = util.Uint8Array2str(openpgp.crypto.generateSessionKey('aes256')); var ElgamalUnencryptedData = new openpgp.MPI(); ElgamalUnencryptedData.fromBytes(openpgp.crypto.pkcs1.eme.encode(symmKey, ElgamalpubMPIs[0].byteLength())); - var ElgamalEncryptedData = openpgp.crypto.publicKeyEncrypt("elgamal", ElgamalpubMPIs, ElgamalUnencryptedData); + openpgp.crypto.publicKeyEncrypt( + "elgamal", ElgamalpubMPIs, ElgamalUnencryptedData + ).then(ElgamalEncryptedData => { - var data = openpgp.crypto.publicKeyDecrypt("elgamal", ElgamalpubMPIs.concat(ElgamalsecMPIs), ElgamalEncryptedData).write(); - data = util.Uint8Array2str(data.subarray(2, data.length)); + var data = openpgp.crypto.publicKeyDecrypt("elgamal", ElgamalpubMPIs.concat(ElgamalsecMPIs), ElgamalEncryptedData).write(); + data = util.Uint8Array2str(data.subarray(2, data.length)); - var result = openpgp.crypto.pkcs1.eme.decode(data, ElgamalpubMPIs[0].byteLength()); - expect(result).to.equal(symmKey); - done(); + var result = openpgp.crypto.pkcs1.eme.decode(data, ElgamalpubMPIs[0].byteLength()); + expect(result).to.equal(symmKey); + done(); + }); }); }); }); diff --git a/test/crypto/elliptic.js b/test/crypto/elliptic.js index 5a1b0c6d..d2c67d27 100644 --- a/test/crypto/elliptic.js +++ b/test/crypto/elliptic.js @@ -3,10 +3,8 @@ var openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); var chai = require('chai'); -var chaiAsPromised = require('chai-as-promised'); -chai.use(chaiAsPromised); -const expect = chai.expect; - +chai.use(require('chai-as-promised')); +var expect = chai.expect; var bin2bi = function (bytes) { var mpi = new openpgp.MPI(); @@ -265,7 +263,7 @@ describe('Elliptic Curve Cryptography', function () { it('Invalid signature', function (done) { expect(verify_signature( 'secp256k1', 8, [], [], [], secp256k1_dummy_point - )).to.eventually.equal(false).notify(done); + )).to.eventually.be.false.notify(done); }); var p384_message = new Uint8Array([ diff --git a/test/general/ecc.js b/test/general/ecc.js index b4522604..59e19fe5 100644 --- a/test/general/ecc.js +++ b/test/general/ecc.js @@ -2,8 +2,9 @@ var openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); -var chai = require('chai'), - expect = chai.expect; +var chai = require('chai'); +chai.use(require('chai-as-promised')); +var expect = chai.expect; describe('Elliptic Curve Cryptography', function () { var data = { @@ -157,73 +158,76 @@ describe('Elliptic Curve Cryptography', function () { load_priv_key('juliet'); done(); }); - it('Verify clear signed message', function (done) { + it('Verify clear signed message', function () { var pub = load_pub_key('juliet'); var msg = openpgp.cleartext.readArmored(data.juliet.message_signed); - openpgp.verify({publicKeys: [pub], message: msg}).then(function(result) { + return openpgp.verify({publicKeys: [pub], message: msg}).then(function(result) { expect(result).to.exist; expect(result.data.trim()).to.equal(data.juliet.message); expect(result.signatures).to.have.length(1); - expect(result.signatures[0].valid).to.be.true; - done(); + expect(result.signatures[0].valid).to.eventually.be.true; }); }); - it('Sign message', function (done) { + // FIXME is this pattern correct? + it('Sign message', function () { var romeo = load_priv_key('romeo'); openpgp.sign({privateKeys: [romeo], data: data.romeo.message + "\n"}).then(function (signed) { var romeo = load_pub_key('romeo'); var msg = openpgp.cleartext.readArmored(signed.data); - openpgp.verify({publicKeys: [romeo], message: msg}).then(function (result) { + return openpgp.verify({publicKeys: [romeo], message: msg}).then(function (result) { expect(result).to.exist; expect(result.data.trim()).to.equal(data.romeo.message); expect(result.signatures).to.have.length(1); - expect(result.signatures[0].valid).to.be.true; - done(); + expect(result.signatures[0].valid).to.eventually.be.true; }); }); }); - it('Decrypt and verify message', function (done) { + it('Decrypt and verify message', function () { var juliet = load_pub_key('juliet'); var romeo = load_priv_key('romeo'); var msg = openpgp.message.readArmored(data.juliet.message_encrypted); - openpgp.decrypt({privateKey: romeo, publicKeys: [juliet], message: msg}).then(function (result) { + return openpgp.decrypt( + {privateKey: romeo, publicKeys: [juliet], message: msg} + ).then(function (result) { expect(result).to.exist; // trim required because https://github.com/openpgpjs/openpgpjs/issues/311 expect(result.data.trim()).to.equal(data.juliet.message); expect(result.signatures).to.have.length(1); - expect(result.signatures[0].valid).to.be.true; - done(); + expect(result.signatures[0].valid).to.eventually.be.true; }); }); - it('Encrypt and sign message', function (done) { + it('Encrypt and sign message', function () { var romeo = load_priv_key('romeo'); var juliet = load_pub_key('juliet'); - openpgp.encrypt({publicKeys: [juliet], privateKeys: [romeo], data: data.romeo.message + "\n"}).then(function (encrypted) { + expect(romeo.decrypt(data['romeo'].pass)).to.be.true; + openpgp.encrypt( + {publicKeys: [juliet], privateKeys: [romeo], data: data.romeo.message + "\n"} + ).then(function (encrypted) { var message = openpgp.message.readArmored(encrypted.data); var romeo = load_pub_key('romeo'); var juliet = load_priv_key('juliet'); - openpgp.decrypt({privateKey: juliet, publicKeys: [romeo], message: message}).then(function (result) { + return openpgp.decrypt( + {privateKey: juliet, publicKeys: [romeo], message: message} + ).then(function (result) { expect(result).to.exist; expect(result.data.trim()).to.equal(data.romeo.message); expect(result.signatures).to.have.length(1); - expect(result.signatures[0].valid).to.be.true; - done(); + expect(result.signatures[0].valid).to.eventually.be.true; }); }); }); - it('Generate key', function (done) { + it('Generate key', function () { var options = { userIds: {name: "Hamlet (secp256k1)", email: "hamlet@example.net"}, curve: "secp256k1", passphrase: "ophelia" }; - openpgp.generateKey(options).then(function (key) { + return openpgp.generateKey(options).then(function (key) { expect(key).to.exist; expect(key.key).to.exist; expect(key.key.primaryKey).to.exist; expect(key.privateKeyArmored).to.exist; expect(key.publicKeyArmored).to.exist; - done(); }); }); }); diff --git a/test/general/key.js b/test/general/key.js index 4ab27a69..4eab2195 100644 --- a/test/general/key.js +++ b/test/general/key.js @@ -2,8 +2,9 @@ var openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); -var chai = require('chai'), - expect = chai.expect; +var chai = require('chai'); +chai.use(require('chai-as-promised')); +var expect = chai.expect; describe('Key', function() { var twoKeys = @@ -718,9 +719,9 @@ describe('Key', function() { expect(pubKey.subKeys).to.exist; expect(pubKey.subKeys).to.have.length(2); - var status = pubKey.subKeys[0].verify(pubKey.primaryKey); - expect(status).to.equal(openpgp.enums.keyStatus.revoked); - done(); + expect(pubKey.subKeys[0].verify( + pubKey.primaryKey + )).to.eventually.equal(openpgp.enums.keyStatus.revoked).notify(done); }); it('Evaluate key flags to find valid encryption key packet', function() { @@ -751,73 +752,96 @@ describe('Key', function() { expect(pubKey.subKeys[0].getExpirationTime().toISOString()).to.be.equal('2018-11-26T10:58:29.000Z'); }); - it('update() - throw error if fingerprints not equal', function() { + it('update() - throw error if fingerprints not equal', function(done) { var keys = openpgp.key.readArmored(twoKeys).keys; - expect(keys[0].update.bind(keys[0], keys[1])).to.throw('Key update method: fingerprints of keys not equal'); + expect(keys[0].update.bind( + keys[0], keys[1] + )()).to.be.rejectedWith('Key update method: fingerprints of keys not equal').notify(done); }); - it('update() - merge revocation signature', function() { + it('update() - merge revocation signature', function(done) { var source = openpgp.key.readArmored(pub_revoked).keys[0]; var dest = openpgp.key.readArmored(pub_revoked).keys[0]; expect(source.revocationSignature).to.exist; dest.revocationSignature = null; - dest.update(source); - expect(dest.revocationSignature).to.exist.and.be.an.instanceof(openpgp.packet.Signature); + dest.update(source).then(() => { + expect(dest.revocationSignature).to.exist.and.be.an.instanceof(openpgp.packet.Signature); + done(); + }); }); - it('update() - merge user', function() { + it('update() - merge user', function(done) { var source = openpgp.key.readArmored(pub_sig_test).keys[0]; var dest = openpgp.key.readArmored(pub_sig_test).keys[0]; expect(source.users[1]).to.exist; dest.users.pop(); - dest.update(source); - expect(dest.users[1]).to.exist; - expect(dest.users[1].userId).to.equal(source.users[1].userId); + dest.update(source).then(() => { + expect(dest.users[1]).to.exist; + expect(dest.users[1].userId).to.equal(source.users[1].userId); + done(); + }); }); - it('update() - merge user - other and revocation certification', function() { + it('update() - merge user - other and revocation certification', function(done) { var source = openpgp.key.readArmored(pub_sig_test).keys[0]; var dest = openpgp.key.readArmored(pub_sig_test).keys[0]; expect(source.users[1].otherCertifications).to.exist; expect(source.users[1].revocationCertifications).to.exist; dest.users[1].otherCertifications = null; dest.users[1].revocationCertifications.pop(); - dest.update(source); - expect(dest.users[1].otherCertifications).to.exist.and.to.have.length(1); - expect(dest.users[1].otherCertifications[0].signature).to.equal(source.users[1].otherCertifications[0].signature); - expect(dest.users[1].revocationCertifications).to.exist.and.to.have.length(2); - expect(dest.users[1].revocationCertifications[1].signature).to.equal(source.users[1].revocationCertifications[1].signature); + dest.update(source).then(() => { + expect(dest.users[1].otherCertifications).to.exist.and.to.have.length(1); + expect(dest.users[1].otherCertifications[0].signature).to.equal(source.users[1].otherCertifications[0].signature); + expect(dest.users[1].revocationCertifications).to.exist.and.to.have.length(2); + expect(dest.users[1].revocationCertifications[1].signature).to.equal(source.users[1].revocationCertifications[1].signature); + done(); + }); }); - it('update() - merge subkey', function() { + it('update() - merge subkey', function(done) { var source = openpgp.key.readArmored(pub_sig_test).keys[0]; var dest = openpgp.key.readArmored(pub_sig_test).keys[0]; expect(source.subKeys[1]).to.exist; dest.subKeys.pop(); - dest.update(source); - expect(dest.subKeys[1]).to.exist; - expect(dest.subKeys[1].subKey.getKeyId().toHex()).to.equal(source.subKeys[1].subKey.getKeyId().toHex()); + dest.update(source).then(() => { + expect(dest.subKeys[1]).to.exist; + expect( + dest.subKeys[1].subKey.getKeyId().toHex() + ).to.equal(source.subKeys[1].subKey.getKeyId().toHex()); + done(); + }); }); - it('update() - merge subkey - revocation signature', function() { + it('update() - merge subkey - revocation signature', function(done) { var source = openpgp.key.readArmored(pub_sig_test).keys[0]; var dest = openpgp.key.readArmored(pub_sig_test).keys[0]; expect(source.subKeys[0].revocationSignature).to.exist; dest.subKeys[0].revocationSignature = null; - dest.update(source); - expect(dest.subKeys[0].revocationSignature).to.exist; - expect(dest.subKeys[0].revocationSignature.signature).to.equal(dest.subKeys[0].revocationSignature.signature); + dest.update(source).then(() => { + expect(dest.subKeys[0].revocationSignature).to.exist; + expect(dest.subKeys[0].revocationSignature.signature).to.equal(dest.subKeys[0].revocationSignature.signature); + done(); + }); }); it('update() - merge private key into public key', function() { var source = openpgp.key.readArmored(priv_key_rsa).keys[0]; var dest = openpgp.key.readArmored(twoKeys).keys[0]; expect(dest.isPublic()).to.be.true; - dest.update(source); - expect(dest.isPrivate()).to.be.true; - expect(source.verifyPrimaryKey()).to.equal(dest.verifyPrimaryKey()); - expect(source.users[0].verify(source.primaryKey)).to.equal(dest.users[0].verify(dest.primaryKey)); - expect(source.subKeys[0].verify(source.primaryKey)).to.equal(dest.subKeys[0].verify(dest.primaryKey)); + return dest.update(source).then(() => { + expect(dest.isPrivate()).to.be.true; + return Promise.all([ + dest.verifyPrimaryKey().then(result => { + expect(source.verifyPrimaryKey()).to.eventually.equal(result); + }), + dest.users[0].verify(dest.primaryKey).then(result => { + expect(source.users[0].verify(source.primaryKey)).to.eventually.equal(result); + }), + dest.subKeys[0].verify(dest.primaryKey).then(result => { + expect(source.subKeys[0].verify(source.primaryKey)).to.eventually.equal(result); + }) + ]); + }); }); it('update() - merge private key into public key - no subkeys', function() { @@ -826,30 +850,42 @@ describe('Key', function() { source.subKeys = null; dest.subKeys = null; expect(dest.isPublic()).to.be.true; - dest.update(source); - expect(dest.isPrivate()).to.be.true; - expect(source.verifyPrimaryKey()).to.equal(dest.verifyPrimaryKey()); - expect(source.users[0].verify(source.primaryKey)).to.equal(dest.users[0].verify(dest.primaryKey)); + return dest.update(source).then(() => { + expect(dest.isPrivate()).to.be.true; + return Promise.all([ + dest.verifyPrimaryKey().then(result => { + expect(source.verifyPrimaryKey()).to.eventually.equal(result); + }), + dest.users[0].verify(dest.primaryKey).then(result => { + expect(source.users[0].verify(source.primaryKey)).to.eventually.equal(result); + }) + ]); + }); }); - it('update() - merge private key into public key - mismatch throws error', function() { + it('update() - merge private key into public key - mismatch throws error', function(done) { var source = openpgp.key.readArmored(priv_key_rsa).keys[0]; var dest = openpgp.key.readArmored(twoKeys).keys[0]; source.subKeys = null; expect(dest.subKeys).to.exist; expect(dest.isPublic()).to.be.true; - expect(dest.update.bind(dest, source)).to.throw('Cannot update public key with private key if subkey mismatch'); + expect(dest.update.bind(dest, source)()) + .to.be.rejectedWith('Cannot update public key with private key if subkey mismatch').notify(done); }); - it('update() - merge subkey binding signatures', function() { + it('update() - merge subkey binding signatures', function(done) { var source = openpgp.key.readArmored(pgp_desktop_pub).keys[0]; var dest = openpgp.key.readArmored(pgp_desktop_priv).keys[0]; expect(source.subKeys[0].bindingSignatures[0]).to.exist; - expect(source.subKeys[0].verify(source.primaryKey)).to.equal(openpgp.enums.keyStatus.valid); + expect(source.subKeys[0].verify(source.primaryKey)) + .to.eventually.equal(openpgp.enums.keyStatus.valid); expect(dest.subKeys[0].bindingSignatures[0]).to.not.exist; - dest.update(source); - expect(dest.subKeys[0].bindingSignatures[0]).to.exist; - expect(dest.subKeys[0].verify(source.primaryKey)).to.equal(openpgp.enums.keyStatus.valid); + dest.update(source).then(() => { + expect(dest.subKeys[0].bindingSignatures[0]).to.exist; + expect(dest.subKeys[0].verify(source.primaryKey)) + .to.eventually.equal(openpgp.enums.keyStatus.valid); + done(); + }); }); it('getPreferredSymAlgo() - one key - AES256', function() { @@ -1002,14 +1038,16 @@ describe('Key', function() { var key = openpgp.key.readArmored(pub_sig_test).keys[0]; var privateKey = openpgp.key.readArmored(priv_key_rsa).keys[0]; privateKey.decrypt('hello world'); - key = key.signPrimaryUser([privateKey]); - var signatures = key.verifyPrimaryUser([privateKey]); - expect(signatures.length).to.equal(2); - expect(signatures[0].keyid.toHex()).to.equal(key.getSigningKeyPacket().getKeyId().toHex()); - expect(signatures[0].valid).to.be.null; - expect(signatures[1].keyid.toHex()).to.equal(privateKey.getSigningKeyPacket().getKeyId().toHex()); - expect(signatures[1].valid).to.be.true; - done(); + key.signPrimaryUser([privateKey]).then(key => { + key.verifyPrimaryUser([privateKey]).then(signatures => { + expect(signatures.length).to.equal(2); + expect(signatures[0].keyid.toHex()).to.equal(key.getSigningKeyPacket().getKeyId().toHex()); + expect(signatures[0].valid).to.be.null; + expect(signatures[1].keyid.toHex()).to.equal(privateKey.getSigningKeyPacket().getKeyId().toHex()); + expect(signatures[1].valid).to.be.true; + done(); + }); + }); }); it('Sign key and verify with wrong key - primary user', function(done) { @@ -1017,36 +1055,40 @@ describe('Key', function() { var privateKey = openpgp.key.readArmored(priv_key_rsa).keys[0]; var wrongKey = openpgp.key.readArmored(wrong_key).keys[0]; privateKey.decrypt('hello world'); - key = key.signPrimaryUser([privateKey]); - var signatures = key.verifyPrimaryUser([wrongKey]); - expect(signatures.length).to.equal(2); - expect(signatures[0].keyid.toHex()).to.equal(key.getSigningKeyPacket().getKeyId().toHex()); - expect(signatures[0].valid).to.be.null; - expect(signatures[1].keyid.toHex()).to.equal(privateKey.getSigningKeyPacket().getKeyId().toHex()); - expect(signatures[1].valid).to.be.null; - done(); + key.signPrimaryUser([privateKey]).then(key => { + key.verifyPrimaryUser([wrongKey]).then(signatures => { + expect(signatures.length).to.equal(2); + expect(signatures[0].keyid.toHex()).to.equal(key.getSigningKeyPacket().getKeyId().toHex()); + expect(signatures[0].valid).to.be.null; + expect(signatures[1].keyid.toHex()).to.equal(privateKey.getSigningKeyPacket().getKeyId().toHex()); + expect(signatures[1].valid).to.be.null; + done(); + }); + }); }); it('Sign and verify key - all users', function(done) { var key = openpgp.key.readArmored(multi_uid_key).keys[0]; var privateKey = openpgp.key.readArmored(priv_key_rsa).keys[0]; privateKey.decrypt('hello world'); - key = key.signAllUsers([privateKey]); - var signatures = key.verifyAllUsers([privateKey]); - expect(signatures.length).to.equal(4); - expect(signatures[0].userid).to.equal(key.users[0].userId.userid); - expect(signatures[0].keyid.toHex()).to.equal(key.getSigningKeyPacket().getKeyId().toHex()); - expect(signatures[0].valid).to.be.null; - expect(signatures[1].userid).to.equal(key.users[0].userId.userid); - expect(signatures[1].keyid.toHex()).to.equal(privateKey.getSigningKeyPacket().getKeyId().toHex()); - expect(signatures[1].valid).to.be.true; - expect(signatures[2].userid).to.equal(key.users[1].userId.userid); - expect(signatures[2].keyid.toHex()).to.equal(key.getSigningKeyPacket().getKeyId().toHex()); - expect(signatures[2].valid).to.be.null; - expect(signatures[3].userid).to.equal(key.users[1].userId.userid); - expect(signatures[3].keyid.toHex()).to.equal(privateKey.getSigningKeyPacket().getKeyId().toHex()); - expect(signatures[3].valid).to.be.true; - done(); + key.signAllUsers([privateKey]).then(key => { + key.verifyAllUsers([privateKey]).then(signatures => { + expect(signatures.length).to.equal(4); + expect(signatures[0].userid).to.equal(key.users[0].userId.userid); + expect(signatures[0].keyid.toHex()).to.equal(key.getSigningKeyPacket().getKeyId().toHex()); + expect(signatures[0].valid).to.be.null; + expect(signatures[1].userid).to.equal(key.users[0].userId.userid); + expect(signatures[1].keyid.toHex()).to.equal(privateKey.getSigningKeyPacket().getKeyId().toHex()); + expect(signatures[1].valid).to.be.true; + expect(signatures[2].userid).to.equal(key.users[1].userId.userid); + expect(signatures[2].keyid.toHex()).to.equal(key.getSigningKeyPacket().getKeyId().toHex()); + expect(signatures[2].valid).to.be.null; + expect(signatures[3].userid).to.equal(key.users[1].userId.userid); + expect(signatures[3].keyid.toHex()).to.equal(privateKey.getSigningKeyPacket().getKeyId().toHex()); + expect(signatures[3].valid).to.be.true; + done(); + }); + }); }); it('Sign key and verify with wrong key - all users', function(done) { @@ -1054,22 +1096,24 @@ describe('Key', function() { var privateKey = openpgp.key.readArmored(priv_key_rsa).keys[0]; var wrongKey = openpgp.key.readArmored(wrong_key).keys[0]; privateKey.decrypt('hello world'); - key = key.signAllUsers([privateKey]); - var signatures = key.verifyAllUsers([wrongKey]); - expect(signatures.length).to.equal(4); - expect(signatures[0].userid).to.equal(key.users[0].userId.userid); - expect(signatures[0].keyid.toHex()).to.equal(key.getSigningKeyPacket().getKeyId().toHex()); - expect(signatures[0].valid).to.be.null; - expect(signatures[1].userid).to.equal(key.users[0].userId.userid); - expect(signatures[1].keyid.toHex()).to.equal(privateKey.getSigningKeyPacket().getKeyId().toHex()); - expect(signatures[1].valid).to.be.null; - expect(signatures[2].userid).to.equal(key.users[1].userId.userid); - expect(signatures[2].keyid.toHex()).to.equal(key.getSigningKeyPacket().getKeyId().toHex()); - expect(signatures[2].valid).to.be.null; - expect(signatures[3].userid).to.equal(key.users[1].userId.userid); - expect(signatures[3].keyid.toHex()).to.equal(privateKey.getSigningKeyPacket().getKeyId().toHex()); - expect(signatures[3].valid).to.be.null; - done(); + key.signAllUsers([privateKey]).then(key => { + key.verifyAllUsers([wrongKey]).then(signatures => { + expect(signatures.length).to.equal(4); + expect(signatures[0].userid).to.equal(key.users[0].userId.userid); + expect(signatures[0].keyid.toHex()).to.equal(key.getSigningKeyPacket().getKeyId().toHex()); + expect(signatures[0].valid).to.be.null; + expect(signatures[1].userid).to.equal(key.users[0].userId.userid); + expect(signatures[1].keyid.toHex()).to.equal(privateKey.getSigningKeyPacket().getKeyId().toHex()); + expect(signatures[1].valid).to.be.null; + expect(signatures[2].userid).to.equal(key.users[1].userId.userid); + expect(signatures[2].keyid.toHex()).to.equal(key.getSigningKeyPacket().getKeyId().toHex()); + expect(signatures[2].valid).to.be.null; + expect(signatures[3].userid).to.equal(key.users[1].userId.userid); + expect(signatures[3].keyid.toHex()).to.equal(privateKey.getSigningKeyPacket().getKeyId().toHex()); + expect(signatures[3].valid).to.be.null; + done(); + }); + }); }); it('Reformat key without passphrase', function() { var userId1 = 'test1 '; @@ -1128,7 +1172,7 @@ describe('Key', function() { return openpgp.encrypt({data: 'hello', publicKeys: newKey.toPublic(), privateKeys: newKey, armor: true}).then(function(encrypted) { return openpgp.decrypt({message: openpgp.message.readArmored(encrypted.data), privateKey: newKey, publicKeys: newKey.toPublic()}).then(function(decrypted) { expect(decrypted.data).to.equal('hello'); - expect(decrypted.signatures[0].valid).to.be.true; + expect(decrypted.signatures[0].valid).to.eventually.be.true; }); }); }); diff --git a/test/general/openpgp.js b/test/general/openpgp.js index b3e4e92e..6070b3a2 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -5,8 +5,9 @@ var openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); var sinon = require('sinon'), - chai = require('chai'), - expect = chai.expect; + chai = require('chai'); +chai.use(require('chai-as-promised')); +var expect = chai.expect; var pub_key = ['-----BEGIN PGP PUBLIC KEY BLOCK-----', @@ -711,7 +712,7 @@ describe('OpenPGP.js public api tests', function() { return openpgp.decrypt(decOpt); }).then(function(decrypted) { expect(decrypted.data).to.equal(plaintext); - expect(decrypted.signatures[0].valid).to.be.true; + expect(decrypted.signatures[0].valid).to.eventually.be.true; expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); expect(decrypted.signatures[0].signature.packets.length).to.equal(1); }); @@ -755,7 +756,7 @@ describe('OpenPGP.js public api tests', function() { return openpgp.decrypt(decOpt); }).then(function(decrypted) { expect(decrypted.data).to.equal(plaintext); - expect(decrypted.signatures[0].valid).to.be.true; + expect(decrypted.signatures[0].valid).to.eventually.be.true; expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); expect(decrypted.signatures[0].signature.packets.length).to.equal(1); }); @@ -788,7 +789,7 @@ describe('OpenPGP.js public api tests', function() { return openpgp.decrypt(decOpt); }).then(function(decrypted) { expect(decrypted.data).to.equal(plaintext); - expect(decrypted.signatures[0].valid).to.be.true; + expect(decrypted.signatures[0].valid).to.eventually.be.true; expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); expect(decrypted.signatures[0].signature.packets.length).to.equal(1); }); @@ -825,10 +826,10 @@ describe('OpenPGP.js public api tests', function() { return openpgp.decrypt(decOpt); }).then(function(decrypted) { expect(decrypted.data).to.equal(plaintext); - expect(decrypted.signatures[0].valid).to.be.true; + expect(decrypted.signatures[0].valid).to.eventually.be.true; expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); expect(decrypted.signatures[0].signature.packets.length).to.equal(1); - expect(decrypted.signatures[1].valid).to.be.true; + expect(decrypted.signatures[1].valid).to.eventually.be.true; expect(decrypted.signatures[1].keyid.toHex()).to.equal(privKeyDE.getSigningKeyPacket().getKeyId().toHex()); expect(decrypted.signatures[1].signature.packets.length).to.equal(1); }); @@ -997,7 +998,7 @@ describe('OpenPGP.js public api tests', function() { return openpgp.verify(verifyOpt); }).then(function(verified) { expect(verified.data).to.equal(plaintext); - expect(verified.signatures[0].valid).to.be.true; + expect(verified.signatures[0].valid).to.eventually.be.true; expect(verified.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); expect(verified.signatures[0].signature.packets.length).to.equal(1); }); @@ -1018,7 +1019,7 @@ describe('OpenPGP.js public api tests', function() { return openpgp.verify(verifyOpt); }).then(function(verified) { expect(verified.data).to.equal(plaintext); - expect(verified.signatures[0].valid).to.be.true; + expect(verified.signatures[0].valid).to.eventually.be.true; expect(verified.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); expect(verified.signatures[0].signature.packets.length).to.equal(1); }); @@ -1078,7 +1079,7 @@ describe('OpenPGP.js public api tests', function() { return openpgp.verify(verifyOpt); }).then(function(verified) { expect(verified.data).to.equal(plaintext); - expect(verified.signatures[0].valid).to.be.true; + expect(verified.signatures[0].valid).to.eventually.be.true; expect(verified.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); expect(verified.signatures[0].signature.packets.length).to.equal(1); }); @@ -1100,7 +1101,7 @@ describe('OpenPGP.js public api tests', function() { return openpgp.verify(verifyOpt); }).then(function(verified) { expect(verified.data).to.equal(plaintext); - expect(verified.signatures[0].valid).to.be.true; + expect(verified.signatures[0].valid).to.eventually.be.true; expect(verified.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); expect(verified.signatures[0].signature.packets.length).to.equal(1); }); @@ -1125,7 +1126,7 @@ describe('OpenPGP.js public api tests', function() { }).then(function(encrypted) { expect(encrypted.data).to.exist; expect(encrypted.data).to.equal(plaintext); - expect(encrypted.signatures[0].valid).to.be.true; + expect(encrypted.signatures[0].valid).to.eventually.be.true; expect(encrypted.signatures[0].keyid.toHex()).to.equal(privKeyDE.getSigningKeyPacket().getKeyId().toHex()); expect(encrypted.signatures[0].signature.packets.length).to.equal(1); }); diff --git a/test/general/packet.js b/test/general/packet.js index 95c03d78..33282cc0 100644 --- a/test/general/packet.js +++ b/test/general/packet.js @@ -2,6 +2,10 @@ var openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); +var chai = require('chai'); +chai.use(require('chai-as-promised')); +var expect = chai.expect; + function stringify(array) { if(!Uint8Array.prototype.isPrototypeOf(array)) { throw new Error('Data must be in the form of a Uint8Array'); @@ -14,9 +18,6 @@ function stringify(array) { return result.join(''); } -var chai = require('chai'), - expect = chai.expect; - describe("Packet", function() { var armored_key = '-----BEGIN PGP PRIVATE KEY BLOCK-----\n' + @@ -191,16 +192,17 @@ describe("Packet", function() { enc.publicKeyAlgorithm = 'rsa_encrypt'; enc.sessionKeyAlgorithm = 'aes256'; enc.publicKeyId.bytes = '12345678'; - enc.encrypt({ params: mpi }); + enc.encrypt({ params: mpi }).then(() => { - msg.push(enc); + msg.push(enc); - msg2.read(msg.write()); + msg2.read(msg.write()); - msg2[0].decrypt({ params: mpi }); + msg2[0].decrypt({ params: mpi }); - expect(stringify(msg2[0].sessionKey)).to.equal(stringify(enc.sessionKey)); - expect(msg2[0].sessionKeyAlgorithm).to.equal(enc.sessionKeyAlgorithm); + expect(stringify(msg2[0].sessionKey)).to.equal(stringify(enc.sessionKey)); + expect(msg2[0].sessionKeyAlgorithm).to.equal(enc.sessionKeyAlgorithm); + }); }); }); @@ -239,12 +241,13 @@ describe("Packet", function() { enc.sessionKeyAlgorithm = 'aes256'; enc.publicKeyId.bytes = '12345678'; - enc.encrypt(key); + enc.encrypt(key).then(() => { - enc.decrypt(key); + enc.decrypt(key); - expect(stringify(enc.sessionKey)).to.equal(stringify(secret)); - done(); + expect(stringify(enc.sessionKey)).to.equal(stringify(secret)); + done(); + }); }); it('Public key encrypted packet (reading, GPG)', function(done) { @@ -374,25 +377,21 @@ describe("Packet", function() { done(); }); - it('Secret key reading with signature verification.', function(done) { + it('Secret key reading with signature verification.', function() { var key = new openpgp.packet.List(); key.read(openpgp.armor.decode(armored_key).data); - - - var verified = key[2].verify(key[0], + return Promise.all([ + expect(key[2].verify(key[0], { userid: key[1], key: key[0] - }); - - verified = verified && key[4].verify(key[0], + })).to.eventually.be.true, + expect(key[4].verify(key[0], { key: key[0], bind: key[3] - }); - - expect(verified).to.be.true; - done(); + })).to.eventually.be.true + ]); }); it('Reading a signed, encrypted message.', function(done) { @@ -424,10 +423,9 @@ describe("Packet", function() { var payload = msg[1].packets[0].packets; - var verified = payload[2].verify(key[0], payload[1]); - - expect(verified).to.be.true; - done(); + expect(payload[2].verify( + key[0], payload[1] + )).to.eventually.be.true.notify(done); }); it('Writing and encryption of a secret key packet.', function() { @@ -486,19 +484,18 @@ describe("Packet", function() { signature.publicKeyAlgorithm = 'rsa_sign'; signature.signatureType = 'binary'; - signature.sign(key, literal); + signature.sign(key, literal).then(() => { - signed.push(literal); - signed.push(signature); + signed.push(literal); + signed.push(signature); - var raw = signed.write(); + var raw = signed.write(); - var signed2 = new openpgp.packet.List(); - signed2.read(raw); + var signed2 = new openpgp.packet.List(); + signed2.read(raw); - var verified = signed2[1].verify(key, signed2[0]); - - expect(verified).to.be.true; + expect(signed2[1].verify(key, signed2[0])).to.eventually.be.true; + }); }); }); }); diff --git a/test/general/signature.js b/test/general/signature.js index 7c287205..57c0eff2 100644 --- a/test/general/signature.js +++ b/test/general/signature.js @@ -2,8 +2,9 @@ var openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); -var chai = require('chai'), - expect = chai.expect; +var chai = require('chai'); +chai.use(require('chai-as-promised')); +var expect = chai.expect; describe("Signature", function() { var priv_key_arm1 = @@ -341,7 +342,7 @@ describe("Signature", function() { priv_key.decrypt("abcd"); return openpgp.decrypt({ privateKey: priv_key, publicKeys:[pub_key], message:msg }).then(function(decrypted) { expect(decrypted.data).to.exist; - expect(decrypted.signatures[0].valid).to.be.true; + expect(decrypted.signatures[0].valid).to.eventually.be.true; expect(decrypted.signatures[0].signature.packets.length).to.equal(1); }); }); @@ -382,15 +383,16 @@ describe("Signature", function() { priv_key_gnupg_ext.subKeys[0].subKey.decrypt("abcd"); return msg.decrypt(priv_key_gnupg_ext).then(function(msg) { - var verified = msg.verify([pub_key]); - expect(verified).to.exist; - expect(verified).to.have.length(1); - expect(verified[0].valid).to.be.true; - expect(verified[0].signature.packets.length).to.equal(1); + return msg.verify([pub_key]).then(verified => { + expect(verified).to.exist; + expect(verified).to.have.length(1); + expect(verified[0].valid).to.eventually.be.true; + expect(verified[0].signature.packets.length).to.equal(1); + }); }); }); - it('Verify V4 signature. Hash: SHA1. PK: RSA. Signature Type: 0x00 (binary document)', function(done) { + it('Verify V4 signature. Hash: SHA1. PK: RSA. Signature Type: 0x00 (binary document)', function() { var signedArmor = [ '-----BEGIN PGP MESSAGE-----', 'Version: GnuPG v2.0.19 (GNU/Linux)', @@ -406,15 +408,15 @@ describe("Signature", function() { var sMsg = openpgp.message.readArmored(signedArmor); var pub_key = openpgp.key.readArmored(pub_key_arm2).keys[0]; - var verified = sMsg.verify([pub_key]); - expect(verified).to.exist; - expect(verified).to.have.length(1); - expect(verified[0].valid).to.be.true; - expect(verified[0].signature.packets.length).to.equal(1); - done(); + return sMsg.verify([pub_key]).then(verified => { + expect(verified).to.exist; + expect(verified).to.have.length(1); + expect(verified[0].valid).to.eventually.be.true; + expect(verified[0].signature.packets.length).to.equal(1); + }); }); - it('Verify V3 signature. Hash: MD5. PK: RSA. Signature Type: 0x01 (text document)', function(done) { + it('Verify V3 signature. Hash: MD5. PK: RSA. Signature Type: 0x01 (text document)', function() { var signedArmor = [ '-----BEGIN PGP MESSAGE-----', 'Version: GnuPG v2.0.19 (GNU/Linux)', @@ -430,12 +432,12 @@ describe("Signature", function() { var sMsg = openpgp.message.readArmored(signedArmor); var pub_key = openpgp.key.readArmored(pub_key_arm2).keys[0]; - var verified = sMsg.verify([pub_key]); - expect(verified).to.exist; - expect(verified).to.have.length(1); - expect(verified[0].valid).to.be.true; - expect(verified[0].signature.packets.length).to.equal(1); - done(); + sMsg.verify([pub_key]).then(verified => { + expect(verified).to.exist; + expect(verified).to.have.length(1); + expect(verified[0].valid).to.eventually.be.true; + expect(verified[0].signature.packets.length).to.equal(1); + }); }); it('Verify signature of signed and encrypted message from GPG2 with openpgp.decrypt', function() { @@ -468,7 +470,7 @@ describe("Signature", function() { expect(decrypted.data).to.exist; expect(decrypted.data).to.equal(plaintext); expect(decrypted.signatures).to.have.length(1); - expect(decrypted.signatures[0].valid).to.be.true; + expect(decrypted.signatures[0].valid).to.eventually.be.true; expect(decrypted.signatures[0].signature.packets.length).to.equal(1); }); }); @@ -504,13 +506,13 @@ describe("Signature", function() { expect(decrypted.data).to.exist; expect(decrypted.data).to.equal(plaintext); expect(decrypted.signatures).to.have.length(1); - expect(decrypted.signatures[0].valid).to.be.true; + expect(decrypted.signatures[0].valid).to.eventually.be.true; expect(decrypted.signatures[0].signature.packets.length).to.equal(1); }); }); - it('Verify signed message with two one pass signatures', function(done) { + it('Verify signed message with two one pass signatures', function() { var msg_armor = [ '-----BEGIN PGP MESSAGE-----', 'Version: GnuPG v2.0.19 (GNU/Linux)', @@ -542,15 +544,14 @@ describe("Signature", function() { expect(sMsg.getText()).to.equal(plaintext); - var verifiedSig = sMsg.verify([pubKey2, pubKey3]); - - expect(verifiedSig).to.exist; - expect(verifiedSig).to.have.length(2); - expect(verifiedSig[0].valid).to.be.true; - expect(verifiedSig[1].valid).to.be.true; - expect(verifiedSig[0].signature.packets.length).to.equal(1); - expect(verifiedSig[1].signature.packets.length).to.equal(1); - done(); + sMsg.verify([pubKey2, pubKey3]).then(verifiedSig => { + expect(verifiedSig).to.exist; + expect(verifiedSig).to.have.length(2); + expect(verifiedSig[0].valid).to.eventually.be.true; + expect(verifiedSig[1].valid).to.eventually.be.true; + expect(verifiedSig[0].signature.packets.length).to.equal(1); + expect(verifiedSig[1].signature.packets.length).to.equal(1); + }); }); it('Verify cleartext signed message with two signatures with openpgp.verify', function() { @@ -592,8 +593,8 @@ describe("Signature", function() { expect(cleartextSig).to.exist; expect(cleartextSig.data).to.equal(plaintext); expect(cleartextSig.signatures).to.have.length(2); - expect(cleartextSig.signatures[0].valid).to.be.true; - expect(cleartextSig.signatures[1].valid).to.be.true; + expect(cleartextSig.signatures[0].valid).to.eventually.be.true; + expect(cleartextSig.signatures[1].valid).to.eventually.be.true; expect(cleartextSig.signatures[0].signature.packets.length).to.equal(1); expect(cleartextSig.signatures[1].signature.packets.length).to.equal(1); }); @@ -614,7 +615,7 @@ describe("Signature", function() { expect(cleartextSig).to.exist; expect(cleartextSig.data).to.equal(plaintext.replace(/\r/g,'')); expect(cleartextSig.signatures).to.have.length(1); - expect(cleartextSig.signatures[0].valid).to.be.true; + expect(cleartextSig.signatures[0].valid).to.eventually.be.true; expect(cleartextSig.signatures[0].signature.packets.length).to.equal(1); }); @@ -627,6 +628,7 @@ describe("Signature", function() { privKey.getSigningKeyPacket().decrypt('hello world'); return openpgp.sign({ privateKeys:[privKey], data:plaintext }).then(function(signed) { + var csMsg = openpgp.message.readArmored(signed.data); return openpgp.verify({ publicKeys:[pubKey], message:csMsg }); @@ -634,7 +636,7 @@ describe("Signature", function() { expect(cleartextSig).to.exist; expect(cleartextSig.data).to.deep.equal(plaintext); expect(cleartextSig.signatures).to.have.length(1); - expect(cleartextSig.signatures[0].valid).to.be.true; + expect(cleartextSig.signatures[0].valid).to.eventually.be.true; expect(cleartextSig.signatures[0].signature.packets.length).to.equal(1); }); @@ -647,6 +649,7 @@ describe("Signature", function() { privKey.getSigningKeyPacket().decrypt('hello world'); return openpgp.sign({ privateKeys:[privKey], data:plaintext, armor:false }).then(function(signed) { + var csMsg = signed.message; return openpgp.verify({ publicKeys:[pubKey], message:csMsg }); @@ -654,7 +657,7 @@ describe("Signature", function() { expect(cleartextSig).to.exist; expect(cleartextSig.data).to.deep.equal(plaintext); expect(cleartextSig.signatures).to.have.length(1); - expect(cleartextSig.signatures[0].valid).to.be.true; + expect(cleartextSig.signatures[0].valid).to.eventually.be.true; expect(cleartextSig.signatures[0].signature.packets.length).to.equal(1); }); @@ -688,20 +691,16 @@ describe("Signature", function() { it('Verify primary key revocation signature', function(done) { var pubKey = openpgp.key.readArmored(pub_revoked).keys[0]; - - var verified = pubKey.revocationSignature.verify(pubKey.primaryKey, {key: pubKey.primaryKey}); - - expect(verified).to.be.true; - done(); + expect(pubKey.revocationSignature.verify( + pubKey.primaryKey, {key: pubKey.primaryKey} + )).to.eventually.be.true.notify(done); }); it('Verify subkey revocation signature', function(done) { var pubKey = openpgp.key.readArmored(pub_revoked).keys[0]; - - var verified = pubKey.subKeys[0].revocationSignature.verify(pubKey.primaryKey, {key: pubKey.primaryKey, bind: pubKey.subKeys[0].subKey}); - - expect(verified).to.be.true; - done(); + expect(pubKey.subKeys[0].revocationSignature.verify( + pubKey.primaryKey, {key: pubKey.primaryKey, bind: pubKey.subKeys[0].subKey} + )).to.eventually.be.true.notify(done); }); it('Verify key expiration date', function(done) { @@ -715,11 +714,7 @@ describe("Signature", function() { it('Verify V3 certification signature', function(done) { var pubKey = openpgp.key.readArmored(pub_v3).keys[0]; - - var verified = pubKey.users[0].selfCertifications[0].verify(pubKey.primaryKey, {key: pubKey.primaryKey, userid: pubKey.users[0].userId}); - - expect(verified).to.be.true; - done(); + expect(pubKey.users[0].selfCertifications[0].verify(pubKey.primaryKey, {key: pubKey.primaryKey, userid: pubKey.users[0].userId})).to.eventually.be.true.notify(done); }); it('Write unhashed subpackets', function() { @@ -785,8 +780,9 @@ describe("Signature", function() { var publicKeys = openpgp.key.readArmored(publicKeyArmored).keys; var msg = openpgp.message.readSignedContent(content, detachedSig); - var result = msg.verify(publicKeys); - expect(result[0].valid).to.be.true; + return msg.verify(publicKeys).then(result => { + expect(result[0].valid).to.eventually.be.true; + }); }); it('Detached signature signing and verification', function() { @@ -799,10 +795,12 @@ describe("Signature", function() { if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys return openpgp.generateKey(opt).then(function(gen) { var generatedKey = gen.key; - var detachedSig = msg.signDetached([generatedKey, privKey2]); - var result = msg.verifyDetached(detachedSig, [generatedKey.toPublic(), pubKey2]); - expect(result[0].valid).to.be.true; - expect(result[1].valid).to.be.true; + return msg.signDetached([generatedKey, privKey2]).then(detachedSig => { + return msg.verifyDetached(detachedSig, [generatedKey.toPublic(), pubKey2]).then(result => { + expect(result[0].valid).to.eventually.be.true; + expect(result[1].valid).to.eventually.be.true; + }); + }); }); }); @@ -817,7 +815,7 @@ describe("Signature", function() { }); }); - it('Verify signed key', function(done) { + it('Verify signed key', function() { var signedArmor = [ '-----BEGIN PGP PUBLIC KEY BLOCK-----', 'Version: GnuPG v1', @@ -847,12 +845,12 @@ describe("Signature", function() { var signedKey = openpgp.key.readArmored(signedArmor).keys[0]; var signerKey = openpgp.key.readArmored(priv_key_arm1).keys[0]; - var signatures = signedKey.verifyPrimaryUser([signerKey]); - expect(signatures[0].valid).to.be.null; - expect(signatures[0].keyid.toHex()).to.equal(signedKey.primaryKey.getKeyId().toHex()); - expect(signatures[1].valid).to.be.true; - expect(signatures[1].keyid.toHex()).to.equal(signerKey.primaryKey.getKeyId().toHex()); - done(); + return signedKey.verifyPrimaryUser([signerKey]).then(signatures => { + expect(signatures[0].valid).to.be.null; + expect(signatures[0].keyid.toHex()).to.equal(signedKey.primaryKey.getKeyId().toHex()); + expect(signatures[1].valid).to.be.true; + expect(signatures[1].keyid.toHex()).to.equal(signerKey.primaryKey.getKeyId().toHex()); + }); }); });