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.
This commit is contained in:
larabr
2023-09-26 15:36:14 +02:00
parent 360a44f57b
commit 7c2248151d
3 changed files with 62 additions and 12 deletions

View File

@@ -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<Object>} 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(', ')}`);

View File

@@ -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 <a@b.com>');
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 <a@b.com>');
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 <a@b.com>');
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 <a@b.com>');
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);

View File

@@ -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' }