diff --git a/src/key.js b/src/key.js index 8f9d23b7..888be85d 100644 --- a/src/key.js +++ b/src/key.js @@ -291,7 +291,6 @@ Key.prototype.getSigningKeyPacket = async function (keyId=null, date=new Date()) }; function isValidEncryptionKeyPacket(keyPacket, signature, date=new Date()) { - const normDate = util.normalizeDate(date); 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) && @@ -342,18 +341,24 @@ Key.prototype.getEncryptionKeyPacket = async function(keyId, date=new Date()) { /** * Encrypts all secret key and subkey packets matching keyId + * @param {String|Array} passphrases - if multiple passphrases, then should be in same order as packets each should encrypt * @param {module:type/keyid} keyId - * @param {String} passphrase * @returns {Promise>} * @async */ -Key.prototype.encrypt = async function(passphrase, keyId=null) { +Key.prototype.encrypt = async function(passphrases, keyId=null) { if (!this.isPrivate()) { throw new Error("Nothing to encrypt in a public key"); } - return Promise.all(this.getKeyPackets(keyId).map(async function(keyPacket) { - await keyPacket.encrypt(passphrase); + const keyPackets = this.getKeyPackets(keyId); + passphrases = util.isArray(passphrases) ? passphrases : new Array(keyPackets.length).fill(passphrases); + if (passphrases.length !== keyPackets.length) { + throw new Error("Invalid number of passphrases for key"); + } + + return Promise.all(keyPackets.map(async function(keyPacket, i) { + await keyPacket.encrypt(passphrases[i]); await keyPacket.clearPrivateParams(); return keyPacket; })); @@ -361,18 +366,32 @@ Key.prototype.encrypt = async function(passphrase, keyId=null) { /** * Decrypts all secret key and subkey packets matching keyId - * @param {String} passphrase + * @param {String|Array} passphrases * @param {module:type/keyid} keyId * @returns {Promise} true if all matching key and subkey packets decrypted successfully * @async */ -Key.prototype.decrypt = async function(passphrase, keyId=null) { +Key.prototype.decrypt = async function(passphrases, keyId=null) { if (!this.isPrivate()) { throw new Error("Nothing to decrypt in a public key"); } + passphrases = util.isArray(passphrases) ? passphrases : [passphrases]; const results = await Promise.all(this.getKeyPackets(keyId).map(async function(keyPacket) { - return keyPacket.decrypt(passphrase); + let decrypted = false; + let error = null; + await Promise.all(passphrases.map(async function(passphrase) { + try { + await keyPacket.decrypt(passphrase); + decrypted = true; + } catch (e) { + error = e; + } + })); + if (!decrypted) { + throw error; + } + return decrypted; })); return results.every(result => result === true); }; @@ -1100,18 +1119,34 @@ export function readArmored(armoredText) { * Assumes already in form of "User Name " * If array is used, the first userId is set as primary user Id * @param {String} options.passphrase The passphrase used to encrypt the resulting private key - * @param {Boolean} [options.unlocked=false] The secret part of the generated key is unlocked * @param {Number} [options.keyExpirationTime=0] * The number of seconds after the key creation time that the key expires + * @param {String} curve (optional) elliptic curve for ECC keys * @param {Date} date Override the creation date of the key and the key signatures + * @param {Array} subkeys (optional) options for each subkey, default to main key options. e.g. [{sign: true, passphrase: '123'}] + * sign parameter defaults to false, and indicates whether the subkey should sign rather than encrypt * @returns {Promise} * @async * @static */ -export function generate(options) { - let secretKeyPacket; - let secretSubkeyPacket; - return Promise.resolve().then(() => { +export async function generate(options) { + options.sign = true; // primary key is always a signing key + options = sanitizeKeyOptions(options); + options.subkeys = options.subkeys.map(function(subkey, index) { return sanitizeKeyOptions(options.subkeys[index], options); }); + + let promises = [generateSecretKey(options)]; + promises = promises.concat(options.subkeys.map(generateSecretSubkey)); + return Promise.all(promises).then(packets => wrapKeyObject(packets[0], packets.slice(1), options)); + + function sanitizeKeyOptions(options, subkeyDefaults={}) { + options.curve = options.curve || subkeyDefaults.curve; + options.numBits = options.numBits || subkeyDefaults.numBits; + options.keyExpirationTime = options.keyExpirationTime !== undefined ? options.keyExpirationTime : subkeyDefaults.keyExpirationTime; + options.passphrase = util.isString(options.passphrase) ? options.passphrase : subkeyDefaults.passphrase; + options.date = options.date || subkeyDefaults.date; + + options.sign = options.sign || false; + if (options.curve) { try { options.curve = enums.write(enums.curve, options.curve); @@ -1119,81 +1154,64 @@ export function generate(options) { throw new Error('Not valid curve.'); } if (options.curve === enums.curve.ed25519 || options.curve === enums.curve.curve25519) { - options.keyType = options.keyType || enums.publicKey.eddsa; + if (options.sign) { + options.algorithm = enums.publicKey.eddsa; + options.curve = enums.curve.ed25519; + } else { + options.algorithm = enums.publicKey.ecdh; + options.curve = enums.curve.curve25519; + } } else { - options.keyType = options.keyType || enums.publicKey.ecdsa; + if (options.sign) { + options.algorithm = enums.publicKey.ecdsa; + } else { + options.algorithm = enums.publicKey.ecdh; + } } - options.subkeyType = options.subkeyType || enums.publicKey.ecdh; } else if (options.numBits) { - options.keyType = options.keyType || enums.publicKey.rsa_encrypt_sign; - options.subkeyType = options.subkeyType || enums.publicKey.rsa_encrypt_sign; + options.algorithm = enums.publicKey.rsa_encrypt_sign; } else { - throw new Error('Key type not specified.'); + throw new Error('Unrecognized key type'); } - - if (options.keyType !== enums.publicKey.rsa_encrypt_sign && - 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 - throw new Error('Unsupported subkey type'); - } - - if (!options.passphrase) { // Key without passphrase is unlocked by definition - options.unlocked = true; - } - if (util.isString(options.userIds)) { - options.userIds = [options.userIds]; - } - - return Promise.all([generateSecretKey(), generateSecretSubkey()]).then(() => wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options)); - }); - - function generateSecretKey() { - secretKeyPacket = new packet.SecretKey(options.date); - 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); + return options; } - function generateSecretSubkey() { - secretSubkeyPacket = new packet.SecretSubkey(options.date); + async function generateSecretKey(options) { + const secretKeyPacket = new packet.SecretKey(options.date); 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); + secretKeyPacket.algorithm = enums.read(enums.publicKey, options.algorithm); + await secretKeyPacket.generate(options.numBits, options.curve); + return secretKeyPacket; + } + + async function generateSecretSubkey(options) { + const secretSubkeyPacket = new packet.SecretSubkey(options.date); + secretSubkeyPacket.packets = null; + secretSubkeyPacket.algorithm = enums.read(enums.publicKey, options.algorithm); + await secretSubkeyPacket.generate(options.numBits, options.curve); + return secretSubkeyPacket; } } /** - * Reformats and signs an OpenPGP with a given User ID. Currently only supports RSA keys. + * Reformats and signs an OpenPGP key with a given User ID. Currently only supports RSA keys. * @param {module:key.Key} options.privateKey The private key to reformat * @param {module:enums.publicKey} [options.keyType=module:enums.publicKey.rsa_encrypt_sign] * @param {String|Array} options.userIds * Assumes already in form of "User Name " * If array is used, the first userId is set as primary user Id * @param {String} options.passphrase The passphrase used to encrypt the resulting private key - * @param {Boolean} [options.unlocked=false] The secret part of the generated key is unlocked * @param {Number} [options.keyExpirationTime=0] * The number of seconds after the key creation time that the key expires + * @param {Date} date Override the creation date of the key and the key signatures + * @param {Array} subkeys (optional) options for each subkey, default to main key options. e.g. [{sign: true, passphrase: '123'}] + * * @returns {Promise} * @async * @static */ export async function reformat(options) { - let secretKeyPacket; - let secretSubkeyPacket; - options.keyType = options.keyType || enums.publicKey.rsa_encrypt_sign; - // RSA Encrypt-Only and RSA Sign-Only are deprecated and SHOULD NOT be generated - if (options.keyType !== enums.publicKey.rsa_encrypt_sign) { - throw new Error('Only RSA Encrypt or Sign supported'); - } + options = sanitizeKeyOptions(options); try { const isDecrypted = options.privateKey.getKeyPackets().every(keyPacket => keyPacket.isDecrypted); @@ -1204,37 +1222,54 @@ export async function reformat(options) { throw new Error('Key not decrypted'); } - if (!options.passphrase) { // Key without passphrase is unlocked by definition - options.unlocked = true; - } - if (util.isString(options.userIds)) { - options.userIds = [options.userIds]; - } const packetlist = options.privateKey.toPacketlist(); + let secretKeyPacket; + const secretSubkeyPackets = []; for (let i = 0; i < packetlist.length; i++) { if (packetlist[i].tag === enums.packet.secretKey) { secretKeyPacket = packetlist[i]; - options.keyType = secretKeyPacket.algorithm; } else if (packetlist[i].tag === enums.packet.secretSubkey) { - secretSubkeyPacket = packetlist[i]; - options.subkeyType = secretSubkeyPacket.algorithm; + secretSubkeyPackets.push(packetlist[i]); } } if (!secretKeyPacket) { throw new Error('Key does not contain a secret key packet'); } - return wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options); + + if (!options.subkeys) { + options.subkeys = secretSubkeyPackets.map(() => ({})); + } + + if (options.subkeys.length !== secretSubkeyPackets.length) { + throw new Error('Number of subkey options does not match number of subkeys'); + } + + options.subkeys = options.subkeys.map(function(subkey, index) { return sanitizeKeyOptions(options.subkeys[index], options); }); + + return wrapKeyObject(secretKeyPacket, secretSubkeyPackets, options); + + function sanitizeKeyOptions(options, subkeyDefaults={}) { + options.keyExpirationTime = options.keyExpirationTime || subkeyDefaults.keyExpirationTime; + options.passphrase = util.isString(options.passphrase) ? options.passphrase : subkeyDefaults.passphrase; + options.date = options.date || subkeyDefaults.date; + + return options; + } } -async function wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options) { +async function wrapKeyObject(secretKeyPacket, secretSubkeyPackets, options) { // set passphrase protection if (options.passphrase) { await secretKeyPacket.encrypt(options.passphrase); - if (secretSubkeyPacket) { - await secretSubkeyPacket.encrypt(options.passphrase); - } } + await Promise.all(secretSubkeyPackets.map(async function(secretSubkeyPacket, index) { + const subkeyPassphrase = options.subkeys[index].passphrase; + if (subkeyPassphrase) { + await secretSubkeyPacket.encrypt(subkeyPassphrase); + } + })); + const packetlist = new packet.List(); packetlist.push(secretKeyPacket); @@ -1248,7 +1283,7 @@ async function wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options) { dataToSign.key = secretKeyPacket; const signaturePacket = new packet.Signature(options.date); signaturePacket.signatureType = enums.signature.cert_generic; - signaturePacket.publicKeyAlgorithm = options.keyType; + signaturePacket.publicKeyAlgorithm = secretKeyPacket.algorithm; signaturePacket.hashAlgorithm = await getPreferredHashAlgo(secretKeyPacket); signaturePacket.keyFlags = [enums.keyFlags.certify_keys | enums.keyFlags.sign_data]; signaturePacket.preferredSymmetricAlgorithms = []; @@ -1287,31 +1322,41 @@ async function wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options) { }); }); - if (secretSubkeyPacket) { + await Promise.all(secretSubkeyPackets.map(async function(secretSubkeyPacket, index) { + const subkeyOptions = options.subkeys[index]; const dataToSign = {}; dataToSign.key = secretKeyPacket; dataToSign.bind = secretSubkeyPacket; - const subkeySignaturePacket = new packet.Signature(options.date); + const subkeySignaturePacket = new packet.Signature(subkeyOptions.date); subkeySignaturePacket.signatureType = enums.signature.subkey_binding; - subkeySignaturePacket.publicKeyAlgorithm = options.keyType; + subkeySignaturePacket.publicKeyAlgorithm = secretKeyPacket.algorithm; subkeySignaturePacket.hashAlgorithm = await getPreferredHashAlgo(secretSubkeyPacket); - subkeySignaturePacket.keyFlags = [enums.keyFlags.encrypt_communication | enums.keyFlags.encrypt_storage]; - if (options.keyExpirationTime > 0) { - subkeySignaturePacket.keyExpirationTime = options.keyExpirationTime; + subkeySignaturePacket.keyFlags = subkeyOptions.sign ? enums.keyFlags.sign_data : [enums.keyFlags.encrypt_communication | enums.keyFlags.encrypt_storage]; + if (subkeyOptions.keyExpirationTime > 0) { + subkeySignaturePacket.keyExpirationTime = subkeyOptions.keyExpirationTime; subkeySignaturePacket.keyNeverExpires = false; } await subkeySignaturePacket.sign(secretKeyPacket, dataToSign); - packetlist.push(secretSubkeyPacket); - packetlist.push(subkeySignaturePacket); + return { secretSubkeyPacket, subkeySignaturePacket}; + })).then(packets => { + packets.forEach(({ secretSubkeyPacket, subkeySignaturePacket }) => { + packetlist.push(secretSubkeyPacket); + packetlist.push(subkeySignaturePacket); + }); + }); + + // set passphrase protection + if (options.passphrase) { + secretKeyPacket.clearPrivateParams(); } - if (!options.unlocked) { - secretKeyPacket.clearPrivateParams(); - if (secretSubkeyPacket) { + await Promise.all(secretSubkeyPackets.map(async function(secretSubkeyPacket, index) { + const subkeyPassphrase = options.subkeys[index].passphrase; + if (subkeyPassphrase) { secretSubkeyPacket.clearPrivateParams(); } - } + })); return new Key(packetlist); } diff --git a/src/openpgp.js b/src/openpgp.js index a1ef6550..6a7aa6ed 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -99,26 +99,22 @@ export function destroyWorker() { * @param {Array} userIds array of user IDs e.g. [{ name:'Phil Zimmermann', email:'phil@openpgp.org' }] * @param {String} passphrase (optional) The passphrase used to encrypt the resulting private key * @param {Number} numBits (optional) number of bits for RSA keys: 2048 or 4096. + * @param {Number} keyExpirationTime (optional) The number of seconds after the key creation time that the key expires * @param {String} curve (optional) elliptic curve for ECC keys: * curve25519, p256, p384, p521, secp256k1, * brainpoolP256r1, brainpoolP384r1, or brainpoolP512r1. - * @param {Boolean} unlocked (optional) If the returned secret part of the generated key is unlocked - * @param {Number} keyExpirationTime (optional) The number of seconds after the key creation time that the key expires * @param {Date} date (optional) override the creation date of the key and the key signatures + * @param {Array} subkeys (optional) options for each subkey, default to main key options. e.g. [{sign: true, passphrase: '123'}] + * sign parameter defaults to false, and indicates whether the subkey should sign rather than encrypt * @returns {Promise} The generated key object in the form: * { key:Key, privateKeyArmored:String, publicKeyArmored:String } * @async * @static */ -export function generateKey({ - userIds=[], passphrase, numBits=2048, unlocked=false, keyExpirationTime=0, curve="", date=new Date() -} = {}) { +export function generateKey({ userIds=[], passphrase="", numBits=2048, keyExpirationTime=0, curve="", date=new Date(), subkeys=[{}] }) { userIds = formatUserIds(userIds); - const options = { - userIds, passphrase, numBits, unlocked, keyExpirationTime, curve, date - }; - + const options = { userIds, passphrase, numBits, keyExpirationTime, curve, date, subkeys }; if (util.getWebCryptoAll() && numBits < 2048) { throw new Error('numBits should be 2048 or 4096, found: ' + numBits); } @@ -141,22 +137,15 @@ export function generateKey({ * @param {Key} privateKey private key to reformat * @param {Array} userIds array of user IDs e.g. [{ name:'Phil Zimmermann', email:'phil@openpgp.org' }] * @param {String} passphrase (optional) The passphrase used to encrypt the resulting private key - * @param {Boolean} unlocked (optional) If the returned secret part of the generated key is unlocked * @param {Number} keyExpirationTime (optional) The number of seconds after the key creation time that the key expires * @returns {Promise} The generated key object in the form: * { key:Key, privateKeyArmored:String, publicKeyArmored:String } * @async * @static */ -export function reformatKey({ - privateKey, userIds=[], passphrase="", unlocked=false, keyExpirationTime=0 -} = {}) { +export function reformatKey({privateKey, userIds=[], passphrase="", keyExpirationTime=0, date}) { userIds = formatUserIds(userIds); - - const options = { - privateKey, userIds, passphrase, unlocked, keyExpirationTime - }; - + const options = { privateKey, userIds, passphrase, keyExpirationTime, date}; if (asyncProxy) { return asyncProxy.delegate('reformatKey', options); } @@ -172,9 +161,9 @@ export function reformatKey({ /** * Unlock a private key with your passphrase. - * @param {Key} privateKey the private key that is to be decrypted - * @param {String} passphrase the user's passphrase chosen during key generation - * @returns {Promise} the unlocked key object in the form: { key:Key } + * @param {Key} privateKey the private key that is to be decrypted + * @param {String|Array} passphrase the user's passphrase(s) chosen during key generation + * @returns {Promise} the unlocked key object in the form: { key:Key } * @async */ export function decryptKey({ privateKey, passphrase }) { @@ -193,9 +182,9 @@ export function decryptKey({ privateKey, passphrase }) { /** * Lock a private key with your passphrase. - * @param {Key} privateKey the private key that is to be decrypted - * @param {String} passphrase the user's passphrase chosen during key generation - * @returns {Promise} the locked key object in the form: { key:Key } + * @param {Key} privateKey the private key that is to be decrypted + * @param {String|Array} passphrase the user's passphrase(s) chosen during key generation + * @returns {Promise} the locked key object in the form: { key:Key } * @async */ export function encryptKey({ privateKey, passphrase }) { diff --git a/test/general/key.js b/test/general/key.js index 425962a6..e9d09db5 100644 --- a/test/general/key.js +++ b/test/general/key.js @@ -1081,7 +1081,6 @@ describe('Key', function() { const opt = { userIds: { name: 'Test User', email: 'text@example.com' }, passphrase: 'secret', - unlocked: true, date: past }; @@ -1108,6 +1107,47 @@ describe('Key', function() { }); }); + it('Generate key - two subkeys with default values', function() { + const userId = 'test '; + const opt = {curve: 'curve25519', userIds: [userId], passphrase: '123', subkeys:[{},{}]}; + return openpgp.generateKey(opt).then(function(key) { + key = key.key; + expect(key.users.length).to.equal(1); + expect(key.users[0].userId.userid).to.equal(userId); + expect(key.users[0].selfCertifications[0].isPrimaryUserID).to.be.true; + expect(key.subKeys).to.have.lengthOf(2); + expect(key.subKeys[0].subKey.algorithm).to.equal('ecdh'); + expect(key.subKeys[1].subKey.algorithm).to.equal('ecdh'); + }); + }); + + it('Generate key - one signing subkey', function() { + const userId = 'test '; + const opt = {curve: 'curve25519', userIds: [userId], passphrase: '123', subkeys:[{}, {sign: true}]}; + return openpgp.generateKey(opt).then(function(key) { + key = key.key; + expect(key.users.length).to.equal(1); + expect(key.users[0].userId.userid).to.equal(userId); + expect(key.users[0].selfCertifications[0].isPrimaryUserID).to.be.true; + expect(key.subKeys).to.have.lengthOf(2); + expect(key.subKeys[0].subKey.algorithm).to.equal('ecdh'); + expect(key.subKeys[1].subKey.algorithm).to.equal('eddsa'); + }); + }); + + it('Generate key - override main key options for subkey', function() { + const userId = 'test '; + const opt = {numBits: 2048, userIds: [userId], passphrase: '123', subkeys:[{curve: 'curve25519'}]}; + return openpgp.generateKey(opt).then(function(key) { + key = key.key; + expect(key.users.length).to.equal(1); + expect(key.users[0].userId.userid).to.equal(userId); + expect(key.users[0].selfCertifications[0].isPrimaryUserID).to.be.true; + expect(key.primaryKey.algorithm).to.equal('rsa_encrypt_sign'); + expect(key.subKeys[0].subKey.algorithm).to.equal('ecdh'); + }); + }); + it('Encrypt key with new passphrase', async function() { const userId = 'test '; const opt = {numBits: 512, userIds: userId, passphrase: 'passphrase'}; @@ -1261,6 +1301,28 @@ describe('Key', function() { }); }); + it('Reformat key with two subkeys with passphrase', function() { + const userId1 = 'test '; + const userId2 = 'test '; + const now = openpgp.util.normalizeDate(new Date()); + const before = openpgp.util.normalizeDate(new Date(0)); + const opt1 = {curve: 'curve25519', userIds: [userId1], date: now}; + return openpgp.generateKey(opt1).then(function(newKey) { + newKey = newKey.key; + expect(newKey.users[0].userId.userid).to.equal(userId1); + expect(+newKey.primaryKey.created).to.equal(+now); + expect(+newKey.subKeys[0].subKey.created).to.equal(+now); + expect(+newKey.subKeys[0].bindingSignatures[0].created).to.equal(+now); + const opt2 = {privateKey: newKey, userIds: [userId2], date: before}; + return openpgp.reformatKey(opt2).then(function(refKey) { + refKey = refKey.key; + expect(refKey.users.length).to.equal(1); + expect(refKey.users[0].userId.userid).to.equal(userId2); + expect(+refKey.subKeys[0].bindingSignatures[0].created).to.equal(+before); + }); + }); + }); + it('Reformat key with no subkey without passphrase', function() { const userId = 'test1 '; const keys = openpgp.key.readArmored(key_without_subkey).keys; diff --git a/test/general/openpgp.js b/test/general/openpgp.js index 2ff92576..59901a86 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -483,22 +483,22 @@ describe('OpenPGP.js public api tests', function() { }); it('should have default params set', function() { - const now = new Date(); + const now = openpgp.util.normalizeDate(new Date()); const opt = { userIds: { name: 'Test User', email: 'text@example.com' }, passphrase: 'secret', - unlocked: true, - date: now + date: now, + subkeys: [] }; return openpgp.generateKey(opt).then(function(newKey) { expect(keyGenStub.withArgs({ userIds: ['Test User '], passphrase: 'secret', numBits: 2048, - unlocked: true, keyExpirationTime: 0, curve: "", - date: now + date: now, + subkeys: [], }).calledOnce).to.be.true; expect(newKey.key).to.exist; expect(newKey.privateKeyArmored).to.exist; @@ -506,23 +506,6 @@ describe('OpenPGP.js public api tests', function() { }); }); - it('should work for no params', function() { - const now = new Date(); - - return openpgp.generateKey({date: now}).then(function(newKey) { - expect(keyGenStub.withArgs({ - userIds: [], - passphrase: undefined, - numBits: 2048, - unlocked: false, - keyExpirationTime: 0, - curve: "", - date: now - }).calledOnce).to.be.true; - expect(newKey.key).to.exist; - }); - }); - it('should delegate to async proxy', function() { const workerStub = { postMessage: function() {} @@ -533,7 +516,12 @@ describe('OpenPGP.js public api tests', function() { const proxyGenStub = stub(openpgp.getWorker(), 'delegate'); getWebCryptoAllStub.returns(); - openpgp.generateKey(); + const opt = { + userIds: { name: 'Test User', email: 'text@example.com' }, + passphrase: 'secret', + subkeys: [] + }; + openpgp.generateKey(opt); expect(proxyGenStub.calledOnce).to.be.true; expect(keyGenStub.calledOnce).to.be.false; });