diff --git a/.gitignore b/.gitignore index 0d4abbcd8..f1250c72a 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ test/lib/ dist/ openpgp.store/ .nyc_output/ +browserify-cache*.json diff --git a/src/key/key.js b/src/key/key.js index 6e4221ccb..7453d1d6b 100644 --- a/src/key/key.js +++ b/src/key/key.js @@ -864,7 +864,7 @@ class Key { /** * Generates a new OpenPGP subkey, and returns a clone of the Key object with the new subkey added. - * Supports RSA and ECC keys. Defaults to the algorithm and bit size/curve of the primary key. + * Supports RSA and ECC keys. Defaults to the algorithm and bit size/curve of the primary key. DSA primary keys default to RSA subkeys. * @param {String} options.algorithm The subkey algorithm: ECC or RSA * @param {String} options.curve (optional) Elliptic curve for ECC keys * @param {Integer} options.rsaBits (optional) Number of bits for RSA subkeys @@ -889,6 +889,11 @@ class Key { throw new Error("Key is not decrypted"); } const defaultOptions = secretKeyPacket.getAlgorithmInfo(); + defaultOptions.rsaBits = Math.max(defaultOptions.bits || 4096, config.rsaBitsMin); + defaultOptions.curve = defaultOptions.curve || "curve25519"; + if (defaultOptions.algorithm === "dsa") { + defaultOptions.algorithm = "rsa"; + } options = helper.sanitizeKeyOptions(options, defaultOptions); const keyPacket = await helper.generateSecretSubkey(options); const bindingSignature = await helper.createBindingSignature(keyPacket, secretKeyPacket, options); diff --git a/src/openpgp.js b/src/openpgp.js index f032af615..bc2507140 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -66,7 +66,7 @@ if (globalThis.ReadableStream) { * @param {String} algorithm (optional) The primary key algorithm: ECC (default) or RSA * @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} rsaBits (optional) Number of bits for RSA keys, defaults to 4096 + * @param {Number} rsaBits (optional) Number of bits for RSA keys, defaults to 4096, or a higher value equal to config.rsaBitsMin * @param {String} curve (optional) Elliptic curve for ECC keys: * curve25519 (default), p256, p384, p521, secp256k1, * brainpoolP256r1, brainpoolP384r1, or brainpoolP512r1 @@ -79,8 +79,9 @@ if (globalThis.ReadableStream) { * @async * @static */ -export function generateKey({ userIds = [], passphrase = "", algorithm = "ecc", rsaBits = 4096, curve = "curve25519", keyExpirationTime = 0, date = new Date(), subkeys = [{}] }) { +export function generateKey({ userIds = [], passphrase = "", algorithm = "ecc", rsaBits, curve = "curve25519", keyExpirationTime = 0, date = new Date(), subkeys = [{}] }) { userIds = toArray(userIds); + rsaBits = rsaBits || Math.max(4096, config.rsaBitsMin); algorithm = algorithm.toLowerCase(); const options = { userIds, passphrase, algorithm, rsaBits, curve, keyExpirationTime, date, subkeys }; if (algorithm === "rsa" && rsaBits < config.rsaBitsMin) { diff --git a/src/packet/public_key.js b/src/packet/public_key.js index 520ec46f5..65ad08433 100644 --- a/src/packet/public_key.js +++ b/src/packet/public_key.js @@ -231,13 +231,15 @@ class PublicKeyPacket { /** * Returns algorithm information - * @returns {Object} An object of the form {algorithm: String, rsaBits:int, curve:String} + * @returns {Object} An object of the form {algorithm: String, bits:int, curve:String} */ getAlgorithmInfo() { const result = {}; result.algorithm = this.algorithm; - if (this.publicParams.n) { - result.rsaBits = this.publicParams.n.length * 8; + // RSA, DSA or ElGamal public modulo + const modulo = this.publicParams.n || this.publicParams.p; + if (modulo) { + result.bits = modulo.length * 8; } else { result.curve = this.publicParams.oid.getName(); } diff --git a/test/general/key.js b/test/general/key.js index 2bb862740..57bd4f915 100644 --- a/test/general/key.js +++ b/test/general/key.js @@ -1916,6 +1916,34 @@ vqBGKJzmO5q3cECw =X9kJ -----END PGP PRIVATE KEY BLOCK-----`; +const dsaPrivateKey = `-----BEGIN PGP PRIVATE KEY BLOCK----- + +lQNTBF69PO8RCACHP4KLQcYOPGsGV9owTZvxnvHvvrY8W0v8xDUL3y6CLc05srF1 +kQp/81iUfP5g57BEiDpJV95kMh+ulBthIOGnuMCkodJjuBICB4K6BtFTV4Fw1Q5S +S7aLC9beCaMvvGHXsK6MbknYl+IVJY7Zmml1qUSrBIQFGp5kqdhIX4o+OrzZ1zYj +ALicqzD7Zx2VRjGNQv7UKv4CkBOC8ncdnq/4/OQeOYFzVbCOf+sJhTgz6yxjHJVC +fLk7w8l2v1zV11VJuc8cQiQ9g8tjbKgLMsbyzy7gl4m9MSCdinG36XZuPibZrSm0 +H8gKAdd1FT84a3/qU2rtLLR0y8tCxBj89Xx/AQCv7CDmwoU+/yGpBVVl1mh0ZUkA +/VJUhnJfv5MIOIi3AQf8CS9HrEmYJg/A3z0DcvcwIu/9gqpRLTqH1iT5o4BCg2j+ +Cog2ExYkQl1OEPkEQ1lKJSnD8MDwO3BlkJ4cD0VSKxlnwd9dsu9m2+F8T+K1hoA7 +PfH89TjD5HrEaGAYIdivLYSwoTNOO+fY8FoVC0RR9pFNOmjiTU5PZZedOxAql5Os +Hp2bYhky0G9trjo8Mt6CGhvgA3dAKyONftLQr9HSM0GKacFV+nRd9TGCPNZidKU8 +MDa/SB/08y1bBGX5FK5wwiZ6H5qD8VAUobH3kwKlrg0nL00/EqtYHJqvJ2gkT5/v +h8+z4R4TuYiy4kKF2FLPd5OjdA31IVDoVgCwF0WHLgf/X9AiTr/DPs/5dIYN1+hf +UJwqjzr3dlokRwx3CVDcOVsdkWRwb8cvxubbsIorvUrF02IhYjHJMjIHT/zFt2zA ++VPzO4zabUlawWVepPEwrCtXgvn9aXqjhAYbilG3UZamhfstGUmbmvWVDadALwby +EO8u2pfLhI2lep63V/+KtUOLhfk8jKRSvxvxlYAvMi7sK8kB+lYy17XKN+IMYgf8 +gMFV6XGKpdmMSV3jOvat8cI6vnRO0i+g3jANP3PfrFEivat/rVgxo67r4rxezfFn +J29qwB9rgbRgMBGsbDvIlQNV/NWFvHy2uQAEKn5eX4CoLsCZoR2VfK3BwBCxhYDp +/wAA/0GSmI9MlMnLadFNlcX2Bm4i15quZAGF8JxwHbj1dhdUEYq0E1Rlc3QgPHRl +c3RAdGVzdC5pbz6IlAQTEQgAPBYhBAq6lCI5EfrbHP1qZCxnOy/rlEGVBQJevTzv +AhsDBQsJCAcCAyICAQYVCgkICwIEFgIDAQIeBwIXgAAKCRAsZzsv65RBlUPoAP9Q +aTCWpHWZkvZzC8VU64O76fHp31rLWlcZFttuDNLyeAEAhOxkQHk6GR88R+EF5mrn +clr63t9Q4wreqOlO0NR5/9k= +=UW2O +-----END PGP PRIVATE KEY BLOCK----- +`; + const uidlessKey = `-----BEGIN PGP PRIVATE KEY BLOCK----- xcMFBF8/lc8BCACwwWWyNdfZ9Qjz8zc4sFGNfHXITscT7WCMuXgC2BbFwiSD @@ -2314,7 +2342,7 @@ function versionSpecificTests() { expect(key.users[0].userId.userid).to.equal(userId); expect(key.users[0].selfCertifications[0].isPrimaryUserID).to.be.true; expect(key.getAlgorithmInfo().algorithm).to.equal('rsaEncryptSign'); - expect(key.getAlgorithmInfo().rsaBits).to.equal(opt.rsaBits); + expect(key.getAlgorithmInfo().bits).to.equal(opt.rsaBits); expect(key.subKeys[0].getAlgorithmInfo().algorithm).to.equal('ecdh'); } finally { openpgp.config.rsaBitsMin = rsaBitsMin; @@ -3398,23 +3426,36 @@ VYGdb3eNlV8CfoEC const pkN = privateKey.primaryKey.publicParams.n; expect(subkeyN.length).to.be.equal(pkN.length); expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('rsaEncryptSign'); - expect(subKey.getAlgorithmInfo().rsaBits).to.be.equal(privateKey.getAlgorithmInfo().rsaBits); + expect(subKey.getAlgorithmInfo().bits).to.be.equal(privateKey.getAlgorithmInfo().bits); await subKey.verify(newPrivateKey.primaryKey); }); - - it('Add a new rsa subkey to an rsaSign key', async function() { + it('Add a new default subkey to an rsaSign key', async function() { const userId = 'test '; const opt = { algorithm: "rsa", rsaBits, userIds: [userId], subkeys: [] }; - try { - const { key } = await openpgp.generateKey(opt); - expect(key.subKeys).to.have.length(0); - key.keyPacket.algorithm = "rsaSign"; - const newKey = await key.addSubkey(); - expect(newKey.subKeys[0].getAlgorithmInfo().algorithm).to.equal('rsaEncryptSign'); - } finally { - openpgp.config.rsaBitsMin = rsaBitsMin; - } + const { key } = await openpgp.generateKey(opt); + expect(key.subKeys).to.have.length(0); + key.keyPacket.algorithm = "rsaSign"; + const newKey = await key.addSubkey(); + expect(newKey.subKeys[0].getAlgorithmInfo().algorithm).to.equal('rsaEncryptSign'); + }); + + it('Add a new default subkey to an ecc key', async function() { + const userId = 'test '; + const opt = { algorithm: "ecc", userIds: [userId], subkeys: [] }; + const { key } = await openpgp.generateKey(opt); + expect(key.subKeys).to.have.length(0); + const newKey = await key.addSubkey(); + expect(newKey.subKeys[0].getAlgorithmInfo().algorithm).to.equal('ecdh'); + expect(newKey.subKeys[0].getAlgorithmInfo().curve).to.equal('curve25519'); + }); + + it('Add a new default subkey to a dsa key', async function() { + const key = await openpgp.readArmoredKey(dsaPrivateKey); + const total = key.subKeys.length; + const newKey = await key.addSubkey(); + expect(newKey.subKeys[total].getAlgorithmInfo().algorithm).to.equal('rsaEncryptSign'); + expect(newKey.subKeys[total].getAlgorithmInfo().bits).to.equal(Math.max(key.getAlgorithmInfo().bits, openpgp.config.rsaBitsMin)); }); it('should throw when trying to encrypt a subkey separately from key', async function() { @@ -3440,7 +3481,7 @@ VYGdb3eNlV8CfoEC await subKey.verify(importedPrivateKey.primaryKey); }); - it('create and add a new ecc subkey to a ecc key', async function() { + it('create and add a new eddsa subkey to a eddsa key', async function() { const userId = 'test '; const opt = { curve: 'curve25519', userIds: [userId], subkeys:[] }; const privateKey = (await openpgp.generateKey(opt)).key; @@ -3464,6 +3505,24 @@ VYGdb3eNlV8CfoEC await subKey.verify(privateKey.primaryKey); }); + it('create and add a new ecdsa subkey to a eddsa key', async function() { + const userId = 'test '; + const opt = { curve: 'ed25519', userIds: [userId], subkeys:[] }; + const privateKey = (await openpgp.generateKey(opt)).key; + const total = privateKey.subKeys.length; + let newPrivateKey = await privateKey.addSubkey({ curve: 'p256', sign: true }); + newPrivateKey = await openpgp.readArmoredKey(newPrivateKey.armor()); + const subKey = newPrivateKey.subKeys[total]; + expect(subKey).to.exist; + expect(newPrivateKey.subKeys.length).to.be.equal(total + 1); + expect(newPrivateKey.getAlgorithmInfo().curve).to.be.equal('ed25519'); + expect(subKey.getAlgorithmInfo().curve).to.be.equal('p256'); + expect(newPrivateKey.getAlgorithmInfo().algorithm).to.be.equal('eddsa'); + expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('ecdsa'); + + await subKey.verify(privateKey.primaryKey); + }); + it('create and add a new ecc subkey to a rsa key', async function() { const privateKey = await openpgp.readArmoredKey(priv_key_rsa); await privateKey.decrypt('hello world'); @@ -3472,11 +3531,40 @@ VYGdb3eNlV8CfoEC let newPrivateKey = await privateKey.addSubkey(opt2); const armoredKey = newPrivateKey.armor(); newPrivateKey = await openpgp.readArmoredKey(armoredKey); + expect(newPrivateKey.subKeys.length).to.be.equal(total + 1); const subKey = newPrivateKey.subKeys[total]; expect(subKey).to.exist; - expect(newPrivateKey.subKeys.length).to.be.equal(total + 1); - expect(subKey.keyPacket.publicParams.oid.getName()).to.be.equal(openpgp.enums.curve.curve25519); expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('ecdh'); + expect(subKey.getAlgorithmInfo().curve).to.be.equal(openpgp.enums.curve.curve25519); + await subKey.verify(privateKey.primaryKey); + }); + + it('create and add a new rsa subkey to a ecc key', async function() { + const userId = 'test '; + const opt = { curve: 'ed25519', userIds: [userId], subkeys:[] }; + const privateKey = (await openpgp.generateKey(opt)).key; + const total = privateKey.subKeys.length; + let newPrivateKey = await privateKey.addSubkey({ algorithm: "rsa" }); + const armoredKey = newPrivateKey.armor(); + newPrivateKey = await openpgp.readArmoredKey(armoredKey); + const subKey = newPrivateKey.subKeys[total]; + expect(subKey).to.exist; + expect(newPrivateKey.subKeys.length).to.be.equal(total + 1); + expect(subKey.getAlgorithmInfo().bits).to.be.equal(4096); + expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('rsaEncryptSign'); + await subKey.verify(privateKey.primaryKey); + }); + + it('create and add a new rsa subkey to a dsa key', async function() { + const privateKey = await openpgp.readArmoredKey(dsaPrivateKey); + const total = privateKey.subKeys.length; + let newPrivateKey = await privateKey.addSubkey({ algorithm: "rsa", rsaBits: 2048 }); + newPrivateKey = await openpgp.readArmoredKey(newPrivateKey.armor()); + expect(newPrivateKey.subKeys.length).to.be.equal(total + 1); + const subKey = newPrivateKey.subKeys[total]; + expect(subKey).to.exist; + expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('rsaEncryptSign'); + expect(subKey.getAlgorithmInfo().bits).to.be.equal(2048); await subKey.verify(privateKey.primaryKey); });