mirror of
https://github.com/openpgpjs/openpgpjs.git
synced 2025-03-30 15:08:32 +00:00
Re-enable using WebCrypto for X25519 when available
Reverting commit ccb040ae96acd127a29161ffaf3b82b5b18c062f . Firefox has fixed support in v132 (https://bugzilla.mozilla.org/show_bug.cgi?id=1918354) usage of v130 and 131, which have a broken implementation, is now below 1%. Also, Chrome has released support in v133.
This commit is contained in:
parent
6d4a86295e
commit
d5689894f6
@ -11,6 +11,7 @@ import enums from '../../../enums';
|
|||||||
import util from '../../../util';
|
import util from '../../../util';
|
||||||
import computeHKDF from '../../hkdf';
|
import computeHKDF from '../../hkdf';
|
||||||
import { getCipherParams } from '../../cipher';
|
import { getCipherParams } from '../../cipher';
|
||||||
|
import { b64ToUint8Array, uint8ArrayToB64 } from '../../../encoding/base64';
|
||||||
|
|
||||||
const HKDF_INFO = {
|
const HKDF_INFO = {
|
||||||
x25519: util.encodeUTF8('OpenPGP X25519'),
|
x25519: util.encodeUTF8('OpenPGP X25519'),
|
||||||
@ -24,7 +25,22 @@ const HKDF_INFO = {
|
|||||||
*/
|
*/
|
||||||
export async function generate(algo) {
|
export async function generate(algo) {
|
||||||
switch (algo) {
|
switch (algo) {
|
||||||
case enums.publicKey.x25519: {
|
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)
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
if (err.name !== 'NotSupportedError') {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
// k stays in little-endian, unlike legacy ECDH over curve25519
|
// k stays in little-endian, unlike legacy ECDH over curve25519
|
||||||
const k = getRandomBytes(32);
|
const k = getRandomBytes(32);
|
||||||
const { publicKey: A } = x25519.box.keyPair.fromSecretKey(k);
|
const { publicKey: A } = x25519.box.keyPair.fromSecretKey(k);
|
||||||
@ -171,7 +187,26 @@ export function getPayloadSize(algo) {
|
|||||||
*/
|
*/
|
||||||
export async function generateEphemeralEncryptionMaterial(algo, recipientA) {
|
export async function generateEphemeralEncryptionMaterial(algo, recipientA) {
|
||||||
switch (algo) {
|
switch (algo) {
|
||||||
case enums.publicKey.x25519: {
|
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 ephemeralSecretKey = getRandomBytes(getPayloadSize(algo));
|
||||||
const sharedSecret = x25519.scalarMult(ephemeralSecretKey, recipientA);
|
const sharedSecret = x25519.scalarMult(ephemeralSecretKey, recipientA);
|
||||||
assertNonZeroArray(sharedSecret);
|
assertNonZeroArray(sharedSecret);
|
||||||
@ -193,7 +228,23 @@ export async function generateEphemeralEncryptionMaterial(algo, recipientA) {
|
|||||||
|
|
||||||
export async function recomputeSharedSecret(algo, ephemeralPublicKey, A, k) {
|
export async function recomputeSharedSecret(algo, ephemeralPublicKey, A, k) {
|
||||||
switch (algo) {
|
switch (algo) {
|
||||||
case enums.publicKey.x25519: {
|
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;
|
||||||
|
}
|
||||||
const sharedSecret = x25519.scalarMult(k, ephemeralPublicKey);
|
const sharedSecret = x25519.scalarMult(k, ephemeralPublicKey);
|
||||||
assertNonZeroArray(sharedSecret);
|
assertNonZeroArray(sharedSecret);
|
||||||
return sharedSecret;
|
return sharedSecret;
|
||||||
@ -224,3 +275,32 @@ function assertNonZeroArray(sharedSecret) {
|
|||||||
throw new Error('Unexpected low order point');
|
throw new Error('Unexpected low order point');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -234,9 +234,9 @@ export default () => describe('ECDH key exchange @lightweight', function () {
|
|||||||
for (const { vector } of vectors) {
|
for (const { vector } of vectors) {
|
||||||
const lowOrderPoint = util.hexToUint8Array(vector);
|
const lowOrderPoint = util.hexToUint8Array(vector);
|
||||||
const { A: K_A, k: a } = await elliptic_curves.ecdhX.generate(openpgp.enums.publicKey.x25519);
|
const { A: K_A, k: a } = await elliptic_curves.ecdhX.generate(openpgp.enums.publicKey.x25519);
|
||||||
await expect(elliptic_curves.ecdhX.encrypt(openpgp.enums.publicKey.x25519, data, lowOrderPoint)).to.be.rejectedWith(/low order point/);
|
await expect(elliptic_curves.ecdhX.encrypt(openpgp.enums.publicKey.x25519, data, lowOrderPoint)).to.be.rejected; // OperationError, DataError or 'low order point', depending on platform
|
||||||
const dummyWrappedKey = new Uint8Array(32); // expected to be unused
|
const dummyWrappedKey = new Uint8Array(32); // expected to be unused
|
||||||
await expect(elliptic_curves.ecdhX.decrypt(openpgp.enums.publicKey.x25519, lowOrderPoint, dummyWrappedKey, K_A, a)).to.be.rejectedWith(/low order point/);
|
await expect(elliptic_curves.ecdhX.decrypt(openpgp.enums.publicKey.x25519, lowOrderPoint, dummyWrappedKey, K_A, a)).to.be.rejected; // OperationError, DataError or 'low order point', depending on platform
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user