Revert to not using the WebCrypto for X25519 (ECDH only)

Due to missing support in WebKit and Chrome (without experimental flags),
and broken support in Firefox, for now we go back to using a JS implementation.

This change only affects encryption and decryption using X25519.
For signing and verification using Ed25519 we keep relying on
WebCrypto when available (namely in WebKit, Firefox, and Node).
This commit is contained in:
larabr 2024-09-11 10:50:50 +02:00
parent 2b9a07e840
commit ccb040ae96

View File

@ -11,7 +11,6 @@ import enums from '../../../enums';
import util from '../../../util';
import computeHKDF from '../../hkdf';
import { getCipherParams } from '../../cipher';
import { b64ToUint8Array, uint8ArrayToB64 } from '../../../encoding/base64';
const HKDF_INFO = {
x25519: util.encodeUTF8('OpenPGP X25519'),
@ -25,27 +24,12 @@ const HKDF_INFO = {
*/
export async function generate(algo) {
switch (algo) {
case enums.publicKey.x25519:
try {
const webCrypto = util.getWebCrypto();
const webCryptoKey = await webCrypto.generateKey('X25519', true, ['deriveKey', 'deriveBits']);
const privateKey = await webCrypto.exportKey('jwk', webCryptoKey.privateKey);
const publicKey = await webCrypto.exportKey('jwk', webCryptoKey.publicKey);
return {
A: new Uint8Array(b64ToUint8Array(publicKey.x)),
k: b64ToUint8Array(privateKey.d, true)
};
} catch (err) {
if (err.name !== 'NotSupportedError') {
throw err;
}
// k stays in little-endian, unlike legacy ECDH over curve25519
const k = getRandomBytes(32);
const { publicKey: A } = x25519.box.keyPair.fromSecretKey(k);
return { A, k };
}
case enums.publicKey.x25519: {
// k stays in little-endian, unlike legacy ECDH over curve25519
const k = getRandomBytes(32);
const { publicKey: A } = x25519.box.keyPair.fromSecretKey(k);
return { A, k };
}
case enums.publicKey.x448: {
const x448 = await util.getNobleCurve(enums.publicKey.x448);
@ -187,32 +171,13 @@ export function getPayloadSize(algo) {
*/
export async function generateEphemeralEncryptionMaterial(algo, recipientA) {
switch (algo) {
case enums.publicKey.x25519:
try {
const webCrypto = util.getWebCrypto();
const jwk = publicKeyToJWK(algo, recipientA);
const ephemeralKeyPair = await webCrypto.generateKey('X25519', true, ['deriveKey', 'deriveBits']);
const recipientPublicKey = await webCrypto.importKey('jwk', jwk, 'X25519', false, []);
const sharedSecretBuffer = await webCrypto.deriveBits(
{ name: 'X25519', public: recipientPublicKey },
ephemeralKeyPair.privateKey,
getPayloadSize(algo) * 8 // in bits
);
const ephemeralPublicKeyJwt = await webCrypto.exportKey('jwk', ephemeralKeyPair.publicKey);
return {
sharedSecret: new Uint8Array(sharedSecretBuffer),
ephemeralPublicKey: new Uint8Array(b64ToUint8Array(ephemeralPublicKeyJwt.x))
};
} catch (err) {
if (err.name !== 'NotSupportedError') {
throw err;
}
const ephemeralSecretKey = getRandomBytes(getPayloadSize(algo));
const sharedSecret = x25519.scalarMult(ephemeralSecretKey, recipientA);
const { publicKey: ephemeralPublicKey } = x25519.box.keyPair.fromSecretKey(ephemeralSecretKey);
case enums.publicKey.x25519: {
const ephemeralSecretKey = getRandomBytes(getPayloadSize(algo));
const sharedSecret = x25519.scalarMult(ephemeralSecretKey, recipientA);
const { publicKey: ephemeralPublicKey } = x25519.box.keyPair.fromSecretKey(ephemeralSecretKey);
return { ephemeralPublicKey, sharedSecret };
}
return { ephemeralPublicKey, sharedSecret };
}
case enums.publicKey.x448: {
const x448 = await util.getNobleCurve(enums.publicKey.x448);
const ephemeralSecretKey = x448.utils.randomPrivateKey();
@ -228,24 +193,7 @@ export async function generateEphemeralEncryptionMaterial(algo, recipientA) {
export async function recomputeSharedSecret(algo, ephemeralPublicKey, A, k) {
switch (algo) {
case enums.publicKey.x25519:
try {
const webCrypto = util.getWebCrypto();
const privateKeyJWK = privateKeyToJWK(algo, A, k);
const ephemeralPublicKeyJWK = publicKeyToJWK(algo, ephemeralPublicKey);
const privateKey = await webCrypto.importKey('jwk', privateKeyJWK, 'X25519', false, ['deriveKey', 'deriveBits']);
const ephemeralPublicKeyReference = await webCrypto.importKey('jwk', ephemeralPublicKeyJWK, 'X25519', false, []);
const sharedSecretBuffer = await webCrypto.deriveBits(
{ name: 'X25519', public: ephemeralPublicKeyReference },
privateKey,
getPayloadSize(algo) * 8 // in bits
);
return new Uint8Array(sharedSecretBuffer);
} catch (err) {
if (err.name !== 'NotSupportedError') {
throw err;
}
return x25519.scalarMult(k, ephemeralPublicKey);
}
return x25519.scalarMult(k, ephemeralPublicKey);
case enums.publicKey.x448: {
const x448 = await util.getNobleCurve(enums.publicKey.x448);
const sharedSecret = x448.getSharedSecret(k, ephemeralPublicKey);
@ -255,32 +203,3 @@ export async function recomputeSharedSecret(algo, ephemeralPublicKey, A, k) {
throw new Error('Unsupported ECDH algorithm');
}
}
function publicKeyToJWK(algo, publicKey) {
switch (algo) {
case enums.publicKey.x25519: {
const jwk = {
kty: 'OKP',
crv: 'X25519',
x: uint8ArrayToB64(publicKey, true),
ext: true
};
return jwk;
}
default:
throw new Error('Unsupported ECDH algorithm');
}
}
function privateKeyToJWK(algo, publicKey, privateKey) {
switch (algo) {
case enums.publicKey.x25519: {
const jwk = publicKeyToJWK(algo, publicKey);
jwk.d = uint8ArrayToB64(privateKey, true);
return jwk;
}
default:
throw new Error('Unsupported ECDH algorithm');
}
}