diff --git a/openpgp.d.ts b/openpgp.d.ts index 931922e0ce..e71e608dc0 100644 --- a/openpgp.d.ts +++ b/openpgp.d.ts @@ -310,6 +310,7 @@ export namespace config { let ignoreMdcError: boolean; let checksumRequired: boolean; let rsaBlinding: boolean; + let minRsaBits: number; let passwordCollisionCheck: boolean; let revocationsExpire: boolean; let useNative: boolean; @@ -621,9 +622,10 @@ export type EllipticCurveName = 'ed25519' | 'curve25519' | 'p256' | 'p384' | 'p5 interface KeyOptions { userIds: UserId[]; // generating a key with no user defined results in error passphrase?: string; - numBits?: number; - keyExpirationTime?: number; + type?: 'ecc' | 'rsa'; curve?: EllipticCurveName; + rsaBits?: number; + keyExpirationTime?: number; date?: Date; subkeys?: KeyOptions[]; } diff --git a/src/config/config.js b/src/config/config.js index de7ac280c6..4cfaf95be7 100644 --- a/src/config/config.js +++ b/src/config/config.js @@ -108,6 +108,11 @@ export default { * @property {Boolean} rsaBlinding */ rsaBlinding: true, + /** + * @memberof module:config + * @property {Number} minRsaBits Minimum RSA key size allowed for key generation + */ + minRsaBits: 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..8cb08ce513 100644 --- a/src/key/factory.js +++ b/src/key/factory.js @@ -37,21 +37,16 @@ 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'}] - * sign parameter defaults to false, and indicates whether the subkey should sign rather than encrypt + * By default, primary and subkeys will be of same type. + * @param {ecc|rsa} options.type The primary key algorithm type: ECC or RSA + * @param {String} options.curve Elliptic curve for ECC keys + * @param {Integer} options.rsaBits Number of bits for RSA keys + * @param {Array} options.userIds User IDs as strings or objects: 'Jo Doe ' or { name:'Jo Doe', email:'info@jo.com' } + * @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 * @static @@ -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 User IDs as strings or objects: 'Jo Doe ' or { name:'Jo Doe', email:'info@jo.com' } + * @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 b9cc41bbf1..f6adb61629 100644 --- a/src/key/helper.js +++ b/src/key/helper.js @@ -327,6 +327,7 @@ export async function isAeadSupported(keys, date = new Date(), userIds = []) { } export function sanitizeKeyOptions(options, subkeyDefaults = {}) { + options.type = options.type || subkeyDefaults.type; options.curve = options.curve || subkeyDefaults.curve; options.rsaBits = options.rsaBits || subkeyDefaults.rsaBits; options.keyExpirationTime = options.keyExpirationTime !== undefined ? options.keyExpirationTime : subkeyDefaults.keyExpirationTime; @@ -335,24 +336,27 @@ export function sanitizeKeyOptions(options, subkeyDefaults = {}) { options.sign = options.sign || false; - if (options.curve) { - 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.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.type) { + case 'ecc': + 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': + options.algorithm = enums.publicKey.rsaEncryptSign; + break; + default: + throw new Error(`Unsupported key type ${options.type}`); } return options; } diff --git a/src/key/key.js b/src/key/key.js index 7ee70f8fc6..5ae500fad8 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'; @@ -861,12 +862,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 {ecc|rsa} options.type 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 @@ -878,14 +879,17 @@ 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.minRsaBits) { + throw new Error(`rsaBits should be at least ${config.minRsaBits}, got: ${options.rsaBits}`); } const secretKeyPacket = this.primaryKey; if (!secretKeyPacket.isDecrypted()) { throw new Error("Key is not decrypted"); } const defaultOptions = secretKeyPacket.getAlgorithmInfo(); + defaultOptions.type = defaultOptions.curve ? 'ecc' : 'rsa'; // DSA keys default to RSA + defaultOptions.rsaBits = defaultOptions.bits || 4096; + defaultOptions.curve = defaultOptions.curve || 'curve25519'; 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..b13434ffaa 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -62,28 +62,28 @@ 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 {ecc|rsa} type (optional) The primary key algorithm type: ECC (default) or RSA + * @param {Array} userIds User IDs as strings or objects: 'Jo Doe ' or { name:'Jo Doe', email:'info@jo.com' } + * @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 {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 = "", type = "ecc", rsaBits = 4096, 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); + const options = { userIds, passphrase, type, rsaBits, curve, keyExpirationTime, date, subkeys }; + if (type === "rsa" && rsaBits < config.minRsaBits) { + throw new Error(`rsaBits should be at least ${config.minRsaBits}, got: ${rsaBits}`); } return generate(options).then(async key => { @@ -103,10 +103,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 User IDs as strings or objects: 'Jo Doe ' or { name:'Jo Doe', email:'info@jo.com' } + * @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/src/packet/secret_key.js b/src/packet/secret_key.js index c3d3f3c7f6..1c24d19e86 100644 --- a/src/packet/secret_key.js +++ b/src/packet/secret_key.js @@ -405,7 +405,6 @@ class SecretKeyPacket extends PublicKeyPacket { } } - async generate(bits, curve) { const algo = enums.write(enums.publicKey, this.algorithm); const { privateParams, publicParams } = await crypto.generateParams(algo, bits, curve); 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 7e70fe574e..46e61e16aa 100644 --- a/test/crypto/validate.js +++ b/test/crypto/validate.js @@ -244,7 +244,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({ type: '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 b2a3a94ef5..9c072fa954 100644 --- a/test/general/key.js +++ b/test/general/key.js @@ -1917,6 +1917,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 @@ -2244,6 +2272,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:[{},{}] }; @@ -2258,20 +2300,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 minRsaBits = openpgp.config.minRsaBits; + openpgp.config.minRsaBits = rsaBits; - return openpgp.generateKey(opt).then(function(key) { - key = key.key; + const userId = 'test '; + const opt = { type: '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.minRsaBits = minRsaBits; + } }); it('Generate key - one signing subkey', function() { @@ -2309,20 +2355,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 minRsaBits = openpgp.config.minRsaBits; + openpgp.config.minRsaBits = 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 = { type: 'rsa', rsaBits, userIds: [userId], passphrase: '123', subkeys:[{ type: '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.minRsaBits = minRsaBits; + } }); it('Encrypt key with new passphrase', async function() { @@ -3407,12 +3457,17 @@ VYGdb3eNlV8CfoEC }); describe('addSubkey functionality testing', function() { - let rsaBits; - let rsaOpt = {}; - if (util.getWebCryptoAll()) { - rsaBits = 2048; - rsaOpt = { rsaBits: rsaBits }; - } + const rsaBits = 1024; + const rsaOpt = { type: 'rsa' }; + let minRsaBits; + beforeEach(function() { + minRsaBits = openpgp.config.minRsaBits; + openpgp.config.minRsaBits = rsaBits; + }); + afterEach(function() { + openpgp.config.minRsaBits = minRsaBits; + }); + 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'); @@ -3425,12 +3480,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 = { type: '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 = { type: '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.minRsaBits)); + }); + 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'); @@ -3454,7 +3537,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; @@ -3478,19 +3561,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 = { type: '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({ type: '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({ type: '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 2c0a831396..c85e223895 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, Key, readArmoredCleartextMessage, CleartextMessage, enums, PacketList, SignaturePacket } = openpgp; const key = require('../../src/key'); @@ -12,7 +11,8 @@ const expect = chai.expect; async function generateTestData() { const victimPrivKey = await key.generate({ userIds: ['Victim '], - rsaBits: util.getWebCryptoAll() ? 2048 : 1024, + type: 'rsa', + rsaBits: 1024, subkeys: [{ sign: true }] @@ -21,7 +21,8 @@ async function generateTestData() { const attackerPrivKey = await key.generate({ userIds: ['Attacker '], - rsaBits: util.getWebCryptoAll() ? 2048 : 1024, + type: 'rsa', + rsaBits: 1024, subkeys: [], sign: false });