From a54c16000b71b43e3f206e3f0df042c756c6a149 Mon Sep 17 00:00:00 2001 From: larabr <7375870+larabr@users.noreply.github.com> Date: Thu, 12 Nov 2020 19:23:58 +0100 Subject: [PATCH] Add explicit algorithm param in generateKey and add config.rsaBitsMin --- .gitignore | 1 + src/config/config.js | 5 + src/key/factory.js | 39 +++---- src/key/helper.js | 47 ++++++--- src/key/key.js | 22 ++-- src/openpgp.js | 42 ++++---- src/packet/public_key.js | 9 +- test/crypto/rsa.js | 10 +- test/crypto/validate.js | 2 +- test/general/key.js | 184 +++++++++++++++++++++++++++++----- test/security/subkey_trust.js | 7 +- 11 files changed, 261 insertions(+), 107 deletions(-) diff --git a/.gitignore b/.gitignore index 0d4abbcd8d..f1250c72a1 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ test/lib/ dist/ openpgp.store/ .nyc_output/ +browserify-cache*.json diff --git a/src/config/config.js b/src/config/config.js index 9948f09e30..75dc2e2acc 100644 --- a/src/config/config.js +++ b/src/config/config.js @@ -106,6 +106,11 @@ export default { * @property {Boolean} rsaBlinding */ rsaBlinding: true, + /** + * @memberof module:config + * @property {Number} rsaBitsMin Minimum RSA key size allowed for key generation + */ + rsaBitsMin: 2048, /** * Work-around for rare GPG decryption bug when encrypting with multiple passwords. * **Slower and slightly less secure** diff --git a/src/key/factory.js b/src/key/factory.js index e9f40bfd28..96fb4ed54e 100644 --- a/src/key/factory.js +++ b/src/key/factory.js @@ -37,20 +37,15 @@ import { unarmor } from '../encoding/armor'; /** * 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.rsaEncryptSign] - * To indicate what type of key to make. - * RSA is 1. See {@link https://tools.ietf.org/html/rfc4880#section-9.1} - * @param {Integer} options.rsaBits number of bits for the key creation. - * @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 {Number} [options.keyExpirationTime=0] - * The number of seconds after the key creation time that the key expires - * @param {String} options.curve (optional) elliptic curve for ECC keys - * @param {Date} options.date Override the creation date of the key and the key signatures - * @param {Array} options.subkeys (optional) options for each subkey, default to main key options. e.g. [{sign: true, passphrase: '123'}] + * By default, primary and subkeys will be of same type. + * @param {String} options.algorithm The primary key algorithm: ECC (default) or RSA + * @param {Integer} options.rsaBits Number of bits for RSA keys + * @param {String} options.curve Elliptic curve for ECC keys + * @param {Array} options.userIds Array of user IDs e.g. [{ name:'Phil Zimmermann', email:'phil@openpgp.org' }] + * @param {String} options.passphrase Passphrase used to encrypt the resulting private key + * @param {Number} options.keyExpirationTime (optional) Number of seconds from the key creation time after which the key expires + * @param {Date} options.date Creation date of the key and the key signatures + * @param {Array} options.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 @@ -68,16 +63,12 @@ export async function generate(options) { /** * 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.rsaEncryptSign] - * @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 {Number} [options.keyExpirationTime=0] - * The number of seconds after the key creation time that the key expires - * @param {Date} options.date Override the creation date of the key and the key signatures - * @param {Array} options.subkeys (optional) options for each subkey, default to main key options. e.g. [{sign: true, passphrase: '123'}] + * @param {module:key.Key} options.privateKey The private key to reformat + * @param {Array} options.userIds Array of user IDs e.g. [{ name:'Phil Zimmermann', email:'phil@openpgp.org' }] + * @param {String} options.passphrase Passphrase used to encrypt the resulting private key + * @param {Number} options.keyExpirationTime Number of seconds from the key creation time after which the key expires + * @param {Date} options.date Override the creation date of the key and the key signatures + * @param {Array} options.subkeys (optional) options for each subkey, default to main key options. e.g. [{sign: true, passphrase: '123'}] * * @returns {Promise} * @async diff --git a/src/key/helper.js b/src/key/helper.js index 9a7eb4858b..289351997c 100644 --- a/src/key/helper.js +++ b/src/key/helper.js @@ -326,6 +326,7 @@ export async function isAeadSupported(keys, date = new Date(), userIds = []) { } export function sanitizeKeyOptions(options, subkeyDefaults = {}) { + options.algorithm = options.algorithm || subkeyDefaults.algorithm; options.curve = options.curve || subkeyDefaults.curve; options.rsaBits = options.rsaBits || subkeyDefaults.rsaBits; options.keyExpirationTime = options.keyExpirationTime !== undefined ? options.keyExpirationTime : subkeyDefaults.keyExpirationTime; @@ -334,24 +335,40 @@ export function sanitizeKeyOptions(options, subkeyDefaults = {}) { options.sign = options.sign || false; - if (options.curve) { + if (!util.isString(options.algorithm)) { try { - options.curve = enums.write(enums.curve, options.curve); + options.algorithm = enums.read(enums.publicKey, options.algorithm); } catch (e) { - throw new Error('Not valid curve.'); + throw new Error('Invalid key algorithm'); } - if (options.curve === enums.curve.ed25519 || options.curve === enums.curve.curve25519) { - options.curve = options.sign ? enums.curve.ed25519 : enums.curve.curve25519; - } - if (options.sign) { - options.algorithm = options.curve === enums.curve.ed25519 ? enums.publicKey.eddsa : enums.publicKey.ecdsa; - } else { - options.algorithm = enums.publicKey.ecdh; - } - } else if (options.rsaBits) { - options.algorithm = enums.publicKey.rsaEncryptSign; - } else { - throw new Error('Unrecognized key type'); + } + switch (options.algorithm) { + case "ecc": + case "eddsa": + case "ecdsa": + case "ecdh": + try { + options.curve = enums.write(enums.curve, options.curve); + } catch (e) { + throw new Error('Invalid curve'); + } + if (options.curve === enums.curve.ed25519 || options.curve === enums.curve.curve25519) { + options.curve = options.sign ? enums.curve.ed25519 : enums.curve.curve25519; + } + if (options.sign) { + options.algorithm = options.curve === enums.curve.ed25519 ? enums.publicKey.eddsa : enums.publicKey.ecdsa; + } else { + options.algorithm = enums.publicKey.ecdh; + } + break; + case "rsa": + case "rsaEncryptSign": + case "rsaEncrypt": + case "rsaSign": + options.algorithm = enums.publicKey.rsaEncryptSign; + break; + default: + throw new Error(`Unsupported key algorithm ${options.algorithm}`); } return options; } diff --git a/src/key/key.js b/src/key/key.js index 2e33937b58..7453d1d6b3 100644 --- a/src/key/key.js +++ b/src/key/key.js @@ -32,6 +32,7 @@ import { PublicSubkeyPacket, SignaturePacket } from '../packet'; +import config from '../config'; import enums from '../enums'; import util from '../util'; import User from './user'; @@ -863,12 +864,12 @@ 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. - * @param {Integer} options.rsaBits number of bits for the key creation. - * @param {Number} [options.keyExpirationTime=0] - * The number of seconds after the key creation time that the key expires - * @param {String} options.curve (optional) Elliptic curve for ECC keys - * @param {Date} options.date (optional) Override the creation date of the key and the key signatures + * 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 + * @param {Number} options.keyExpirationTime (optional) Number of seconds from the key creation time after which the key expires + * @param {Date} options.date (optional) Override the creation date of the key and the key signatures * @param {Boolean} options.sign (optional) Indicates whether the subkey should sign rather than encrypt. Defaults to false * @returns {Promise} * @async @@ -880,14 +881,19 @@ class Key { if (options.passphrase) { throw new Error("Subkey could not be encrypted here, please encrypt whole key"); } - if (util.getWebCryptoAll() && options.rsaBits < 2048) { - throw new Error('When using webCrypto rsaBits should be 2048 or 4096, found: ' + options.rsaBits); + if (options.rsaBits < config.rsaBitsMin) { + throw new Error(`rsaBits should be at least ${config.rsaBitsMin}, got: ${options.rsaBits}`); } const secretKeyPacket = this.primaryKey; if (!secretKeyPacket.isDecrypted()) { 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 a3d302f9ae..bc25071406 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -62,28 +62,30 @@ if (globalThis.ReadableStream) { /** - * Generates a new OpenPGP key pair. Supports RSA and ECC keys. Primary and subkey will be of same type. - * @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: 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 {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 + * Generates a new OpenPGP key pair. Supports RSA and ECC keys. By default, primary and subkeys will be of same type. + * @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, 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 + * @param {Date} date (optional) Override the creation date of the key and the key signatures + * @param {Number} keyExpirationTime (optional) Number of seconds from the key creation time after which the key expires + * @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, revocationCertificate:String } * @async * @static */ -export function generateKey({ userIds = [], passphrase = "", rsaBits = null, keyExpirationTime = 0, curve = "curve25519", date = new Date(), subkeys = [{}] }) { +export function generateKey({ userIds = [], passphrase = "", algorithm = "ecc", rsaBits, curve = "curve25519", keyExpirationTime = 0, date = new Date(), subkeys = [{}] }) { userIds = toArray(userIds); - curve = rsaBits ? "" : curve; - const options = { userIds, passphrase, rsaBits, keyExpirationTime, curve, date, subkeys }; - if (util.getWebCryptoAll() && rsaBits && rsaBits < 2048) { - throw new Error('rsaBits should be 2048 or 4096, found: ' + rsaBits); + 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) { + throw new Error(`rsaBits should be at least ${config.rsaBitsMin}, got: ${rsaBits}`); } return generate(options).then(async key => { @@ -103,10 +105,10 @@ export function generateKey({ userIds = [], passphrase = "", rsaBits = null, key /** * Reformats signature packets for a key and rewraps key object. - * @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 {Number} keyExpirationTime (optional) The number of seconds after the key creation time that the key expires + * @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 {Number} keyExpirationTime (optional) Number of seconds from the key creation time after which the key expires * @returns {Promise} The generated key object in the form: * { key:Key, privateKeyArmored:String, publicKeyArmored:String, revocationCertificate:String } * @async diff --git a/src/packet/public_key.js b/src/packet/public_key.js index b2ac972845..65ad08433a 100644 --- a/src/packet/public_key.js +++ b/src/packet/public_key.js @@ -231,14 +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; - result.bits = result.rsaBits; // Deprecated. + // 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/crypto/rsa.js b/test/crypto/rsa.js index 893cf3a131..71f8a4f201 100644 --- a/test/crypto/rsa.js +++ b/test/crypto/rsa.js @@ -14,7 +14,7 @@ const expect = chai.expect; const native = util.getWebCrypto() || util.getNodeCrypto(); module.exports = () => (!native ? describe.skip : describe)('basic RSA cryptography with native crypto', function () { it('generate rsa key', async function() { - const bits = util.getWebCryptoAll() ? 2048 : 1024; + const bits = 1024; const keyObject = await crypto.publicKey.rsa.generate(bits, 65537); expect(keyObject.n).to.exist; expect(keyObject.e).to.exist; @@ -25,7 +25,7 @@ module.exports = () => (!native ? describe.skip : describe)('basic RSA cryptogra }); it('sign and verify using generated key params', async function() { - const bits = util.getWebCryptoAll() ? 2048 : 1024; + const bits = 1024; const { publicParams, privateParams } = await crypto.generateParams(openpgp.enums.publicKey.rsaSign, bits); const message = await random.getRandomBytes(64); const hash_algo = openpgp.enums.write(openpgp.enums.hash, 'sha256'); @@ -38,7 +38,7 @@ module.exports = () => (!native ? describe.skip : describe)('basic RSA cryptogra }); it('encrypt and decrypt using generated key params', async function() { - const bits = util.getWebCryptoAll() ? 2048 : 1024; + const bits = 1024; const { publicParams, privateParams } = await crypto.generateParams(openpgp.enums.publicKey.rsaSign, bits); const { n, e, d, p, q, u } = { ...publicParams, ...privateParams }; const message = await crypto.generateSessionKey('aes256'); @@ -72,7 +72,7 @@ module.exports = () => (!native ? describe.skip : describe)('basic RSA cryptogra }); it('compare native crypto and bn math sign', async function() { - const bits = util.getWebCrypto() ? 2048 : 1024; + const bits = 1024; const { publicParams, privateParams } = await crypto.generateParams(openpgp.enums.publicKey.rsaSign, bits); const { n, e, d, p, q, u } = { ...publicParams, ...privateParams }; const message = await random.getRandomBytes(64); @@ -98,7 +98,7 @@ module.exports = () => (!native ? describe.skip : describe)('basic RSA cryptogra }); it('compare native crypto and bn math verify', async function() { - const bits = util.getWebCrypto() ? 2048 : 1024; + const bits = 1024; const { publicParams, privateParams } = await crypto.generateParams(openpgp.enums.publicKey.rsaSign, bits); const { n, e, d, p, q, u } = { ...publicParams, ...privateParams }; const message = await random.getRandomBytes(64); diff --git a/test/crypto/validate.js b/test/crypto/validate.js index bfe3c88361..16b00df3d0 100644 --- a/test/crypto/validate.js +++ b/test/crypto/validate.js @@ -238,7 +238,7 @@ module.exports = () => { describe('RSA parameter validation', function() { let rsaKey; before(async () => { - rsaKey = (await openpgp.generateKey({ rsaBits: 2048, userIds: [{ name: 'Test', email: 'test@test.com' }] })).key; + rsaKey = (await openpgp.generateKey({ algorithm: "rsa", rsaBits: 2048, userIds: [{ name: 'Test', email: 'test@test.com' }] })).key; }); it('generated RSA params are valid', async function() { diff --git a/test/general/key.js b/test/general/key.js index 91da96ca44..7d6999ef35 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 @@ -2218,6 +2246,20 @@ function versionSpecificTests() { }); }); + it('Generate key - default values', function() { + const userId = 'test '; + const opt = { userIds: [userId] }; + return openpgp.generateKey(opt).then(function({ key }) { + expect(key.isDecrypted()).to.be.true; + expect(key.getAlgorithmInfo().algorithm).to.equal('eddsa'); + 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.length(1); + expect(key.subKeys[0].getAlgorithmInfo().algorithm).to.equal('ecdh'); + }); + }); + it('Generate key - two subkeys with default values', function() { const userId = 'test '; const opt = { userIds: [userId], passphrase: '123', subkeys:[{},{}] }; @@ -2232,20 +2274,24 @@ function versionSpecificTests() { }); }); - it('Generate RSA key - two subkeys with default values', function() { - const userId = 'test '; - const opt = { rsaBits: 512, userIds: [userId], passphrase: '123', subkeys:[{},{}] }; - if (util.getWebCryptoAll()) { opt.rsaBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys + it('Generate RSA key - two subkeys with default values', async function() { + const rsaBits = 512; + const rsaBitsMin = openpgp.config.rsaBitsMin; + openpgp.config.rsaBitsMin = rsaBits; - return openpgp.generateKey(opt).then(function(key) { - key = key.key; + const userId = 'test '; + const opt = { algorithm: "rsa", rsaBits, userIds: [userId], passphrase: '123', subkeys:[{},{}] }; + try { + const { key } = await openpgp.generateKey(opt); 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.length(2); expect(key.subKeys[0].getAlgorithmInfo().algorithm).to.equal('rsaEncryptSign'); expect(key.subKeys[1].getAlgorithmInfo().algorithm).to.equal('rsaEncryptSign'); - }); + } finally { + openpgp.config.rsaBitsMin = rsaBitsMin; + } }); it('Generate key - one signing subkey', function() { @@ -2283,20 +2329,24 @@ function versionSpecificTests() { }); }); - it('Generate key - override main RSA key options for subkey', function() { + it('Generate key - override main RSA key options for subkey', async function() { + const rsaBits = 512; + const rsaBitsMin = openpgp.config.rsaBitsMin; + openpgp.config.rsaBitsMin = rsaBits; + const userId = 'test '; - const opt = { rsaBits: 512, userIds: [userId], passphrase: '123', subkeys:[{ curve: 'curve25519' }] }; - if (util.getWebCryptoAll()) { opt.rsaBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys - return openpgp.generateKey(opt).then(function(key) { - key = key.key; + const opt = { algorithm: "rsa", rsaBits, userIds: [userId], passphrase: '123', subkeys:[{ algorithm: "ecc", curve: 'curve25519' }] }; + try { + const { key } = await openpgp.generateKey(opt); 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.getAlgorithmInfo().algorithm).to.equal('rsaEncryptSign'); expect(key.getAlgorithmInfo().bits).to.equal(opt.rsaBits); - expect(key.getAlgorithmInfo().rsaBits).to.equal(key.getAlgorithmInfo().bits); expect(key.subKeys[0].getAlgorithmInfo().algorithm).to.equal('ecdh'); - }); + } finally { + openpgp.config.rsaBitsMin = rsaBitsMin; + } }); it('Encrypt key with new passphrase', async function() { @@ -3351,12 +3401,17 @@ VYGdb3eNlV8CfoEC }); describe('addSubkey functionality testing', function() { - let rsaBits; - let rsaOpt = {}; - if (util.getWebCryptoAll()) { - rsaBits = 2048; - rsaOpt = { rsaBits: rsaBits }; - } + const rsaBits = 1024; + const rsaOpt = { algorithm: "rsa" }; + let rsaBitsMin; + beforeEach(function() { + rsaBitsMin = openpgp.config.rsaBitsMin; + openpgp.config.rsaBitsMin = rsaBits; + }); + afterEach(function() { + openpgp.config.rsaBitsMin = rsaBitsMin; + }); + it('create and add a new rsa subkey to stored rsa key', async function() { const privateKey = await openpgp.readArmoredKey(priv_key_rsa); await privateKey.decrypt('hello world'); @@ -3369,12 +3424,40 @@ VYGdb3eNlV8CfoEC expect(newPrivateKey.subKeys.length).to.be.equal(total + 1); const subkeyN = subKey.keyPacket.publicParams.n; const pkN = privateKey.primaryKey.publicParams.n; - expect(subkeyN.length).to.be.equal(rsaBits ? (rsaBits / 8) : pkN.length); + expect(subkeyN.length).to.be.equal(pkN.length); expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('rsaEncryptSign'); - expect(subKey.getAlgorithmInfo().rsaBits).to.be.equal(rsaBits || privateKey.getAlgorithmInfo().rsaBits); + expect(subKey.getAlgorithmInfo().bits).to.be.equal(privateKey.getAlgorithmInfo().bits); await subKey.verify(newPrivateKey.primaryKey); }); + it('Add a new default subkey to an rsaSign key', async function() { + const userId = 'test '; + const opt = { algorithm: "rsa", rsaBits, userIds: [userId], subkeys: [] }; + 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() { const privateKey = await openpgp.readArmoredKey(priv_key_rsa); await privateKey.decrypt('hello world'); @@ -3398,7 +3481,7 @@ VYGdb3eNlV8CfoEC await subKey.verify(importedPrivateKey.primaryKey); }); - it('create and add a new ec subkey to a ec 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; @@ -3422,19 +3505,66 @@ VYGdb3eNlV8CfoEC await subKey.verify(privateKey.primaryKey); }); - it('create and add a new ec subkey to a rsa key', async function() { + 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'); const total = privateKey.subKeys.length; - const opt2 = { curve: 'curve25519' }; + const opt2 = { algorithm: "ecc", curve: 'curve25519' }; 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); }); diff --git a/test/security/subkey_trust.js b/test/security/subkey_trust.js index 95d9dc44ab..17940a78d5 100644 --- a/test/security/subkey_trust.js +++ b/test/security/subkey_trust.js @@ -1,5 +1,4 @@ const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../..'); -const util = require('../../src/util'); const { readArmoredKey, generate, Key, readArmoredCleartextMessage, CleartextMessage, enums, PacketList, SignaturePacket } = openpgp; @@ -11,7 +10,8 @@ const expect = chai.expect; async function generateTestData() { const victimPrivKey = await generate({ userIds: ['Victim '], - rsaBits: util.getWebCryptoAll() ? 2048 : 1024, + algorithm: "rsa", + rsaBits: 1024, subkeys: [{ sign: true }] @@ -20,7 +20,8 @@ async function generateTestData() { const attackerPrivKey = await generate({ userIds: ['Attacker '], - rsaBits: util.getWebCryptoAll() ? 2048 : 1024, + algorithm: "rsa", + rsaBits: 1024, subkeys: [], sign: false });