From d155da23dda001707882db36fa5a1084f10b09af Mon Sep 17 00:00:00 2001 From: larabr <7375870+larabr@users.noreply.github.com> Date: Wed, 9 Jul 2025 16:00:08 +0200 Subject: [PATCH 1/3] Revert "CI: do not test Webkit on Linux" This reverts commit 4762d2c7623eccaf297a2bf9f4c7aa957aa32c6f. --- .github/workflows/tests.yml | 1 - test/web-test-runner.config.js | 10 ++++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index be4ca5a9..62a87a9f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -106,7 +106,6 @@ jobs: npx playwright install --with-deps firefox - name: Install WebKit # caching not possible, external shared libraries required - if: ${{ matrix.runner == 'macos-latest' }} # do not install on ubuntu, since the X25519 WebCrypto implementation has issues run: npx playwright install --with-deps webkit - name: Run browser tests diff --git a/test/web-test-runner.config.js b/test/web-test-runner.config.js index 5fc9c6b2..45e18856 100644 --- a/test/web-test-runner.config.js +++ b/test/web-test-runner.config.js @@ -1,10 +1,10 @@ -import { existsSync } from 'fs'; -import { playwrightLauncher, playwright } from '@web/test-runner-playwright'; +import { playwrightLauncher } from '@web/test-runner-playwright'; const sharedPlaywrightCIOptions = { // createBrowserContext: ({ browser }) => browser.newContext({ ignoreHTTPSErrors: true }), headless: true }; + export default { nodeResolve: true, // to resolve npm module imports in `unittests.html` files: './test/unittests.html', @@ -29,13 +29,11 @@ export default { ...sharedPlaywrightCIOptions, product: 'firefox' }), - // try setting up webkit, but ignore if not available - // (e.g. on ubuntu, where we don't want to test webkit as the WebCrypto X25519 implementation has issues) - existsSync(playwright.webkit.executablePath()) && playwrightLauncher({ + playwrightLauncher({ ...sharedPlaywrightCIOptions, product: 'webkit' }) - ].filter(Boolean) + ] } ] }; From b9275642e12e81a70e6753e97cf079cf12418e8a Mon Sep 17 00:00:00 2001 From: larabr <7375870+larabr@users.noreply.github.com> Date: Wed, 9 Jul 2025 16:21:33 +0200 Subject: [PATCH 2/3] Add workaround for WebCrypto X25519 key export bug on WebKit Linux https://bugs.webkit.org/show_bug.cgi?id=289693 --- src/crypto/public_key/elliptic/ecdh_x.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/crypto/public_key/elliptic/ecdh_x.js b/src/crypto/public_key/elliptic/ecdh_x.js index 608c990a..6a0f562b 100644 --- a/src/crypto/public_key/elliptic/ecdh_x.js +++ b/src/crypto/public_key/elliptic/ecdh_x.js @@ -33,6 +33,12 @@ export async function generate(algo) { const privateKey = await webCrypto.exportKey('jwk', webCryptoKey.privateKey); const publicKey = await webCrypto.exportKey('jwk', webCryptoKey.publicKey); + if (privateKey.x !== publicKey.x) { // Weird issue with Webkit on Linux: https://bugs.webkit.org/show_bug.cgi?id=289693 + const err = new Error('Unexpected mismatching public point'); + err.name = 'NotSupportedError'; + throw err; + } + return { A: new Uint8Array(b64ToUint8Array(publicKey.x)), k: b64ToUint8Array(privateKey.d) @@ -190,15 +196,21 @@ export async function generateEphemeralEncryptionMaterial(algo, recipientA) { case enums.publicKey.x25519: try { const webCrypto = util.getWebCrypto(); - const jwk = publicKeyToJWK(algo, recipientA); const ephemeralKeyPair = await webCrypto.generateKey('X25519', true, ['deriveKey', 'deriveBits']); + const ephemeralPublicKeyJwt = await webCrypto.exportKey('jwk', ephemeralKeyPair.publicKey); + const ephemeralPrivateKeyJwt = await webCrypto.exportKey('jwk', ephemeralKeyPair.privateKey); + if (ephemeralPrivateKeyJwt.x !== ephemeralPublicKeyJwt.x) { // Weird issue with Webkit on Linux: https://bugs.webkit.org/show_bug.cgi?id=289693 + const err = new Error('Unexpected mismatching public point'); + err.name = 'NotSupportedError'; + throw err; + } + const jwk = publicKeyToJWK(algo, recipientA); 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)) From 9703ab891e15ac316952657227ded2c8d47fdf4a Mon Sep 17 00:00:00 2001 From: larabr <7375870+larabr@users.noreply.github.com> Date: Thu, 10 Jul 2025 21:20:32 +0200 Subject: [PATCH 3/3] Add workaround for WebCrypto X25519 key generation bug on WebKit Linux Similar/same issue was already patched for Ed25519 . https://bugs.webkit.org/show_bug.cgi?id=279113 --- src/crypto/public_key/elliptic/ecdh_x.js | 20 ++++++++++++++++++-- src/crypto/public_key/elliptic/eddsa.js | 12 ++++++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/crypto/public_key/elliptic/ecdh_x.js b/src/crypto/public_key/elliptic/ecdh_x.js index 6a0f562b..43ce91b2 100644 --- a/src/crypto/public_key/elliptic/ecdh_x.js +++ b/src/crypto/public_key/elliptic/ecdh_x.js @@ -28,7 +28,15 @@ export async function generate(algo) { case enums.publicKey.x25519: try { const webCrypto = util.getWebCrypto(); - const webCryptoKey = await webCrypto.generateKey('X25519', true, ['deriveKey', 'deriveBits']); + const webCryptoKey = await webCrypto.generateKey('X25519', true, ['deriveKey', 'deriveBits']) + .catch(err => { + if (err.name === 'OperationError') { // Temporary (hopefully) fix for WebKit on Linux + const newErr = new Error('Unexpected key generation issue'); + newErr.name = 'NotSupportedError'; + throw newErr; + } + throw err; + }); const privateKey = await webCrypto.exportKey('jwk', webCryptoKey.privateKey); const publicKey = await webCrypto.exportKey('jwk', webCryptoKey.publicKey); @@ -196,7 +204,15 @@ export async function generateEphemeralEncryptionMaterial(algo, recipientA) { case enums.publicKey.x25519: try { const webCrypto = util.getWebCrypto(); - const ephemeralKeyPair = await webCrypto.generateKey('X25519', true, ['deriveKey', 'deriveBits']); + const ephemeralKeyPair = await webCrypto.generateKey('X25519', true, ['deriveKey', 'deriveBits']) + .catch(err => { + if (err.name === 'OperationError') { // Temporary (hopefully) fix for WebKit on Linux + const newErr = new Error('Unexpected key generation issue'); + newErr.name = 'NotSupportedError'; + throw newErr; + } + throw err; + }); const ephemeralPublicKeyJwt = await webCrypto.exportKey('jwk', ephemeralKeyPair.publicKey); const ephemeralPrivateKeyJwt = await webCrypto.exportKey('jwk', ephemeralKeyPair.privateKey); if (ephemeralPrivateKeyJwt.x !== ephemeralPublicKeyJwt.x) { // Weird issue with Webkit on Linux: https://bugs.webkit.org/show_bug.cgi?id=289693 diff --git a/src/crypto/public_key/elliptic/eddsa.js b/src/crypto/public_key/elliptic/eddsa.js index 79d68db4..86d9f17e 100644 --- a/src/crypto/public_key/elliptic/eddsa.js +++ b/src/crypto/public_key/elliptic/eddsa.js @@ -38,7 +38,15 @@ export async function generate(algo) { case enums.publicKey.ed25519: try { const webCrypto = util.getWebCrypto(); - const webCryptoKey = await webCrypto.generateKey('Ed25519', true, ['sign', 'verify']); + const webCryptoKey = await webCrypto.generateKey('Ed25519', true, ['sign', 'verify']) + .catch(err => { + if (err.name === 'OperationError') { // Temporary (hopefully) fix for WebKit on Linux + const newErr = new Error('Unexpected key generation issue'); + newErr.name = 'NotSupportedError'; + throw newErr; + } + throw err; + }); const privateKey = await webCrypto.exportKey('jwk', webCryptoKey.privateKey); const publicKey = await webCrypto.exportKey('jwk', webCryptoKey.publicKey); @@ -48,7 +56,7 @@ export async function generate(algo) { seed: b64ToUint8Array(privateKey.d, true) }; } catch (err) { - if (err.name !== 'NotSupportedError' && err.name !== 'OperationError') { // Temporary (hopefully) fix for WebKit on Linux + if (err.name !== 'NotSupportedError') { throw err; } const seed = getRandomBytes(getPayloadSize(algo));