From 8d8033383b00ee6d729d15f712303ebf469d981d Mon Sep 17 00:00:00 2001 From: larabr <7375870+larabr@users.noreply.github.com> Date: Mon, 9 Sep 2024 10:37:58 +0200 Subject: [PATCH] Fix regression in x25519 (legacy) key generation: store clamped secret scalar Fixes regression from changes in #1782, as the spec mandates that legacy x25519 store the secret scalar already clamped. Keys generated using v6.0.0-beta.3 are still expected to be functional, since the scalar is to be clamped before computing the ECDH shared secret. --- src/crypto/public_key/elliptic/oid_curves.js | 3 +++ test/crypto/ecdh.js | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/src/crypto/public_key/elliptic/oid_curves.js b/src/crypto/public_key/elliptic/oid_curves.js index a66decdc4..7404cbe6c 100644 --- a/src/crypto/public_key/elliptic/oid_curves.js +++ b/src/crypto/public_key/elliptic/oid_curves.js @@ -182,8 +182,11 @@ class CurveWithOID { case 'node': return nodeGenKeyPair(this.name); case 'curve25519Legacy': { + // the private key must be stored in big endian and already clamped: https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#section-5.5.5.6.1.1-3 const { k, A } = await ecdhXGenerate(enums.publicKey.x25519); const privateKey = k.slice().reverse(); + privateKey[0] = (privateKey[0] & 127) | 64; + privateKey[31] &= 248; const publicKey = util.concatUint8Array([new Uint8Array([this.wireFormatLeadingByte]), A]); return { publicKey, privateKey }; } diff --git a/test/crypto/ecdh.js b/test/crypto/ecdh.js index 54408418a..b44c8a3f3 100644 --- a/test/crypto/ecdh.js +++ b/test/crypto/ecdh.js @@ -1,3 +1,4 @@ +import x25519 from '@openpgp/tweetnacl'; import sinon from 'sinon'; import { use as chaiUse, expect } from 'chai'; import chaiAsPromised from 'chai-as-promised'; // eslint-disable-line import/newline-after-import @@ -67,6 +68,16 @@ export default () => describe('ECDH key exchange @lightweight', function () { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ]); + it('Generated legacy x25519 secret scalar is stored clamped', async function () { + const curve = new elliptic_curves.CurveWithOID(openpgp.enums.curve.curve25519Legacy); + const { privateKey, publicKey } = await curve.genKeyPair(); + const clampedKey = privateKey.slice(); + clampedKey[0] = (clampedKey[0] & 127) | 64; + clampedKey[31] &= 248; + expect(privateKey).to.deep.equal(clampedKey); + const { publicKey: expectedPublicKey } = x25519.box.keyPair.fromSecretKey(privateKey.slice().reverse()); + expect(publicKey.subarray(1)).to.deep.equal(expectedPublicKey); + }); it('Invalid curve oid', function (done) { expect(decrypt_message( '', 2, 7, [], [], [], [], []