Skip to content

Commit ccb040a

Browse files
committed
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).
1 parent 2b9a07e commit ccb040a

1 file changed

Lines changed: 13 additions & 94 deletions

File tree

src/crypto/public_key/elliptic/ecdh_x.js

Lines changed: 13 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import enums from '../../../enums';
1111
import util from '../../../util';
1212
import computeHKDF from '../../hkdf';
1313
import { getCipherParams } from '../../cipher';
14-
import { b64ToUint8Array, uint8ArrayToB64 } from '../../../encoding/base64';
1514

1615
const HKDF_INFO = {
1716
x25519: util.encodeUTF8('OpenPGP X25519'),
@@ -25,27 +24,12 @@ const HKDF_INFO = {
2524
*/
2625
export async function generate(algo) {
2726
switch (algo) {
28-
case enums.publicKey.x25519:
29-
try {
30-
const webCrypto = util.getWebCrypto();
31-
const webCryptoKey = await webCrypto.generateKey('X25519', true, ['deriveKey', 'deriveBits']);
32-
33-
const privateKey = await webCrypto.exportKey('jwk', webCryptoKey.privateKey);
34-
const publicKey = await webCrypto.exportKey('jwk', webCryptoKey.publicKey);
35-
36-
return {
37-
A: new Uint8Array(b64ToUint8Array(publicKey.x)),
38-
k: b64ToUint8Array(privateKey.d, true)
39-
};
40-
} catch (err) {
41-
if (err.name !== 'NotSupportedError') {
42-
throw err;
43-
}
44-
// k stays in little-endian, unlike legacy ECDH over curve25519
45-
const k = getRandomBytes(32);
46-
const { publicKey: A } = x25519.box.keyPair.fromSecretKey(k);
47-
return { A, k };
48-
}
27+
case enums.publicKey.x25519: {
28+
// k stays in little-endian, unlike legacy ECDH over curve25519
29+
const k = getRandomBytes(32);
30+
const { publicKey: A } = x25519.box.keyPair.fromSecretKey(k);
31+
return { A, k };
32+
}
4933

5034
case enums.publicKey.x448: {
5135
const x448 = await util.getNobleCurve(enums.publicKey.x448);
@@ -187,32 +171,13 @@ export function getPayloadSize(algo) {
187171
*/
188172
export async function generateEphemeralEncryptionMaterial(algo, recipientA) {
189173
switch (algo) {
190-
case enums.publicKey.x25519:
191-
try {
192-
const webCrypto = util.getWebCrypto();
193-
const jwk = publicKeyToJWK(algo, recipientA);
194-
const ephemeralKeyPair = await webCrypto.generateKey('X25519', true, ['deriveKey', 'deriveBits']);
195-
const recipientPublicKey = await webCrypto.importKey('jwk', jwk, 'X25519', false, []);
196-
const sharedSecretBuffer = await webCrypto.deriveBits(
197-
{ name: 'X25519', public: recipientPublicKey },
198-
ephemeralKeyPair.privateKey,
199-
getPayloadSize(algo) * 8 // in bits
200-
);
201-
const ephemeralPublicKeyJwt = await webCrypto.exportKey('jwk', ephemeralKeyPair.publicKey);
202-
return {
203-
sharedSecret: new Uint8Array(sharedSecretBuffer),
204-
ephemeralPublicKey: new Uint8Array(b64ToUint8Array(ephemeralPublicKeyJwt.x))
205-
};
206-
} catch (err) {
207-
if (err.name !== 'NotSupportedError') {
208-
throw err;
209-
}
210-
const ephemeralSecretKey = getRandomBytes(getPayloadSize(algo));
211-
const sharedSecret = x25519.scalarMult(ephemeralSecretKey, recipientA);
212-
const { publicKey: ephemeralPublicKey } = x25519.box.keyPair.fromSecretKey(ephemeralSecretKey);
174+
case enums.publicKey.x25519: {
175+
const ephemeralSecretKey = getRandomBytes(getPayloadSize(algo));
176+
const sharedSecret = x25519.scalarMult(ephemeralSecretKey, recipientA);
177+
const { publicKey: ephemeralPublicKey } = x25519.box.keyPair.fromSecretKey(ephemeralSecretKey);
213178

214-
return { ephemeralPublicKey, sharedSecret };
215-
}
179+
return { ephemeralPublicKey, sharedSecret };
180+
}
216181
case enums.publicKey.x448: {
217182
const x448 = await util.getNobleCurve(enums.publicKey.x448);
218183
const ephemeralSecretKey = x448.utils.randomPrivateKey();
@@ -228,24 +193,7 @@ export async function generateEphemeralEncryptionMaterial(algo, recipientA) {
228193
export async function recomputeSharedSecret(algo, ephemeralPublicKey, A, k) {
229194
switch (algo) {
230195
case enums.publicKey.x25519:
231-
try {
232-
const webCrypto = util.getWebCrypto();
233-
const privateKeyJWK = privateKeyToJWK(algo, A, k);
234-
const ephemeralPublicKeyJWK = publicKeyToJWK(algo, ephemeralPublicKey);
235-
const privateKey = await webCrypto.importKey('jwk', privateKeyJWK, 'X25519', false, ['deriveKey', 'deriveBits']);
236-
const ephemeralPublicKeyReference = await webCrypto.importKey('jwk', ephemeralPublicKeyJWK, 'X25519', false, []);
237-
const sharedSecretBuffer = await webCrypto.deriveBits(
238-
{ name: 'X25519', public: ephemeralPublicKeyReference },
239-
privateKey,
240-
getPayloadSize(algo) * 8 // in bits
241-
);
242-
return new Uint8Array(sharedSecretBuffer);
243-
} catch (err) {
244-
if (err.name !== 'NotSupportedError') {
245-
throw err;
246-
}
247-
return x25519.scalarMult(k, ephemeralPublicKey);
248-
}
196+
return x25519.scalarMult(k, ephemeralPublicKey);
249197
case enums.publicKey.x448: {
250198
const x448 = await util.getNobleCurve(enums.publicKey.x448);
251199
const sharedSecret = x448.getSharedSecret(k, ephemeralPublicKey);
@@ -255,32 +203,3 @@ export async function recomputeSharedSecret(algo, ephemeralPublicKey, A, k) {
255203
throw new Error('Unsupported ECDH algorithm');
256204
}
257205
}
258-
259-
260-
function publicKeyToJWK(algo, publicKey) {
261-
switch (algo) {
262-
case enums.publicKey.x25519: {
263-
const jwk = {
264-
kty: 'OKP',
265-
crv: 'X25519',
266-
x: uint8ArrayToB64(publicKey, true),
267-
ext: true
268-
};
269-
return jwk;
270-
}
271-
default:
272-
throw new Error('Unsupported ECDH algorithm');
273-
}
274-
}
275-
276-
function privateKeyToJWK(algo, publicKey, privateKey) {
277-
switch (algo) {
278-
case enums.publicKey.x25519: {
279-
const jwk = publicKeyToJWK(algo, publicKey);
280-
jwk.d = uint8ArrayToB64(privateKey, true);
281-
return jwk;
282-
}
283-
default:
284-
throw new Error('Unsupported ECDH algorithm');
285-
}
286-
}

0 commit comments

Comments
 (0)