From 7c2248151da0d31b1bbd8f06b50708cd8b947327 Mon Sep 17 00:00:00 2001 From: larabr <7375870+larabr@users.noreply.github.com> Date: Tue, 26 Sep 2023 15:36:14 +0200 Subject: [PATCH] Default to generating new curve25519 format for v6 keys As per the spec, v6 keys must not use the legacy curve25519 format. The new format is not used by default with v4 keys as it's not compatible with OpenPGP.js older than v5.10.0 . However, v6 keys already break compatibility, so if the user requests them via config flag, we can safely use the new curve format as well. --- src/openpgp.js | 11 +++++++++-- test/general/key.js | 29 ++++++++++++++++++++--------- test/general/openpgp.js | 34 +++++++++++++++++++++++++++++++++- 3 files changed, 62 insertions(+), 12 deletions(-) diff --git a/src/openpgp.js b/src/openpgp.js index 7fad9aaa..13c93d58 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -37,7 +37,7 @@ import { checkKeyRequirements } from './key/helper'; * The generated primary key will have signing capabilities. By default, one subkey with encryption capabilities is also generated. * @param {Object} options * @param {Object|Array} options.userIDs - User IDs as objects: `{ name: 'Jo Doe', email: 'info@jo.com' }` - * @param {'ecc'|'rsa'|'curve448'|'curve25519'} [options.type='ecc'] - The primary key algorithm type: ECC (default), RSA, Curve448 or Curve25519 (new format). + * @param {'ecc'|'rsa'|'curve448'|'curve25519'} [options.type='ecc'] - The primary key algorithm type: ECC (default for v4 keys), RSA, Curve448 or Curve25519 (new format, default for v6 keys). * Note: Curve448 and Curve25519 (new format) are not widely supported yet. * @param {String} [options.passphrase=(not protected)] - The passphrase used to encrypt the generated private key. If omitted or empty, the key won't be encrypted. * @param {Number} [options.rsaBits=4096] - Number of bits for RSA keys @@ -55,8 +55,15 @@ import { checkKeyRequirements } from './key/helper'; * @async * @static */ -export async function generateKey({ userIDs = [], passphrase, type = 'ecc', rsaBits = 4096, curve = 'curve25519', keyExpirationTime = 0, date = new Date(), subkeys = [{}], format = 'armored', config, ...rest }) { +export async function generateKey({ userIDs = [], passphrase, type, curve, rsaBits = 4096, keyExpirationTime = 0, date = new Date(), subkeys = [{}], format = 'armored', config, ...rest }) { config = { ...defaultConfig, ...config }; checkConfig(config); + if (!type && !curve) { + type = config.v6Keys ? 'curve25519' : 'ecc'; // default to new curve25519 for v6 keys (legacy curve25519 cannot be used with them) + curve = 'curve25519'; // unused with type != 'ecc' + } else { + type = type || 'ecc'; + curve = curve || 'curve25519'; + } userIDs = toArray(userIDs); const unknownOptions = Object.keys(rest); if (unknownOptions.length > 0) throw new Error(`Unknown option: ${unknownOptions.join(', ')}`); diff --git a/test/general/key.js b/test/general/key.js index 2238b510..b30f0166 100644 --- a/test/general/key.js +++ b/test/general/key.js @@ -2463,29 +2463,35 @@ function versionSpecificTests() { }); it('Generate key - default values', function() { + const v6Key = openpgp.config.v6Keys; + const userID = { name: 'test', email: 'a@b.com' }; const opt = { userIDs: [userID], format: 'object' }; return openpgp.generateKey(opt).then(function({ privateKey: key }) { + expect(key.keyPacket.version).to.equal(v6Key ? 6 : 4); expect(key.isDecrypted()).to.be.true; - expect(key.getAlgorithmInfo().algorithm).to.equal('eddsaLegacy'); + expect(key.getAlgorithmInfo().algorithm).to.equal(v6Key ? 'ed25519' : 'eddsaLegacy'); expect(key.users.length).to.equal(1); expect(key.users[0].userID.userID).to.equal('test '); 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'); + expect(key.subkeys[0].getAlgorithmInfo().algorithm).to.equal(v6Key ? 'x25519' : 'ecdh'); }); }); it('Generate key - two subkeys with default values', function() { + const v6Key = openpgp.config.v6Keys; + const userID = { name: 'test', email: 'a@b.com' }; const opt = { userIDs: [userID], passphrase: '123', format: 'object', subkeys:[{},{}] }; return openpgp.generateKey(opt).then(function({ privateKey: key }) { + expect(key.keyPacket.version).to.equal(v6Key ? 6 : 4); expect(key.users.length).to.equal(1); expect(key.users[0].userID.userID).to.equal('test '); 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('ecdh'); - expect(key.subkeys[1].getAlgorithmInfo().algorithm).to.equal('ecdh'); + expect(key.subkeys[0].getAlgorithmInfo().algorithm).to.equal(v6Key ? 'x25519' : 'ecdh'); + expect(key.subkeys[1].getAlgorithmInfo().algorithm).to.equal(v6Key ? 'x25519' : 'ecdh'); }); }); @@ -2558,34 +2564,39 @@ function versionSpecificTests() { }); it('Generate key - one signing subkey', function() { + const v6Key = openpgp.config.v6Keys; const userID = { name: 'test', email: 'a@b.com' }; const opt = { userIDs: [userID], passphrase: '123', format: 'object', subkeys:[{}, { sign: true }] }; + return openpgp.generateKey(opt).then(async function({ privateKey: key }) { + expect(key.keyPacket.version).to.equal(v6Key ? 6 : 4); expect(key.users.length).to.equal(1); expect(key.users[0].userID.userID).to.equal('test '); 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('ecdh'); + expect(key.subkeys[0].getAlgorithmInfo().algorithm).to.equal(v6Key ? 'x25519' : 'ecdh'); expect(await key.getEncryptionKey()).to.equal(key.subkeys[0]); - expect(key.subkeys[1].getAlgorithmInfo().algorithm).to.equal('eddsaLegacy'); + expect(key.subkeys[1].getAlgorithmInfo().algorithm).to.equal(v6Key ? 'ed25519' : 'eddsaLegacy'); expect(await key.getSigningKey()).to.equal(key.subkeys[1]); }); }); it('Reformat key - one signing subkey', async function() { + const v6Key = openpgp.config.v6Keys; const userID = { name: 'test', email: 'a@b.com' }; const opt = { userIDs: [userID], format: 'object', subkeys:[{}, { sign: true }] }; const { privateKey } = await openpgp.generateKey(opt); return openpgp.reformatKey({ privateKey, userIDs: [userID] }).then(async function({ privateKey: armoredKey }) { const key = await openpgp.readKey({ armoredKey }); + expect(key.keyPacket.version).to.equal(v6Key ? 6 : 4); expect(key.users.length).to.equal(1); expect(key.users[0].userID.userID).to.equal('test '); 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('ecdh'); + expect(key.subkeys[0].getAlgorithmInfo().algorithm).to.equal(v6Key ? 'x25519' : 'ecdh'); expect(await key.getEncryptionKey()).to.equal(key.subkeys[0]); - expect(key.subkeys[1].getAlgorithmInfo().algorithm).to.equal('eddsaLegacy'); + expect(key.subkeys[1].getAlgorithmInfo().algorithm).to.equal(v6Key ? 'ed25519' : 'eddsaLegacy'); expect(await key.getSigningKey()).to.equal(key.subkeys[1]); }); }); @@ -2596,7 +2607,7 @@ function versionSpecificTests() { openpgp.config.minRSABits = rsaBits; const userID = { name: 'test', email: 'a@b.com' }; - const opt = { type: 'rsa', rsaBits, userIDs: [userID], passphrase: '123', format: 'object', subkeys:[{ type: 'ecc', curve: 'curve25519' }] }; + const opt = { type: 'rsa', rsaBits, userIDs: [userID], passphrase: '123', format: 'object', subkeys:[{ type: 'ecc', curve: 'p256' }] }; try { const { privateKey: key } = await openpgp.generateKey(opt); expect(key.users.length).to.equal(1); diff --git a/test/general/openpgp.js b/test/general/openpgp.js index e97a21f5..d59c4cd1 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -1012,7 +1012,7 @@ export default () => describe('OpenPGP.js public api tests', function() { }); describe('generateKey - unit tests', function() { - it('should have default params set', function() { + it('should have default params set (v4 key)', function() { const now = util.normalizeDate(new Date()); const opt = { userIDs: { name: 'Test User', email: 'text@example.com' }, @@ -1023,6 +1023,7 @@ export default () => describe('OpenPGP.js public api tests', function() { return openpgp.generateKey(opt).then(async function({ privateKey, publicKey }) { for (const key of [publicKey, privateKey]) { expect(key).to.exist; + expect(key.keyPacket.version).to.equal(4); expect(key.users.length).to.equal(1); expect(key.users[0].userID.name).to.equal('Test User'); expect(key.users[0].userID.email).to.equal('text@example.com'); @@ -1039,6 +1040,37 @@ export default () => describe('OpenPGP.js public api tests', function() { }); }); + it('should have default params set (v6 key)', function() { + const now = util.normalizeDate(new Date()); + const opt = { + userIDs: { name: 'Test User', email: 'text@example.com' }, + passphrase: 'secret', + date: now, + format: 'object', + config: { v6Keys: true } + }; + return openpgp.generateKey(opt).then(async function({ privateKey, publicKey }) { + for (const key of [publicKey, privateKey]) { + expect(key).to.exist; + expect(key.keyPacket.version).to.equal(6); + expect(key.users.length).to.equal(1); + expect(key.users[0].userID.name).to.equal('Test User'); + expect(key.users[0].userID.email).to.equal('text@example.com'); + expect(key.getAlgorithmInfo().algorithm).to.equal('ed25519'); + expect(key.getAlgorithmInfo().rsaBits).to.equal(undefined); + expect(key.getAlgorithmInfo().curve).to.equal(undefined); + expect(+key.getCreationTime()).to.equal(+now); + expect(await key.getExpirationTime()).to.equal(Infinity); + expect(key.subkeys.length).to.equal(1); + expect(key.subkeys[0].getAlgorithmInfo().algorithm).to.equal('x25519'); + expect(key.subkeys[0].getAlgorithmInfo().rsaBits).to.equal(undefined); + expect(key.subkeys[0].getAlgorithmInfo().curve).to.equal(undefined); + expect(+key.subkeys[0].getCreationTime()).to.equal(+now); + expect(await key.subkeys[0].getExpirationTime()).to.equal(Infinity); + } + }); + }); + it('should output keypair with expected format', async function() { const opt = { userIDs: { name: 'Test User', email: 'text@example.com' }