Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master' into synodim
Browse files Browse the repository at this point in the history
  • Loading branch information
csett86 committed Oct 15, 2024
2 parents 675ca72 + 89380ab commit 8cc88e6
Show file tree
Hide file tree
Showing 7 changed files with 55 additions and 30 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
Changes in [1.11.81](https://github.com/element-hq/element-web/releases/tag/v1.11.81) (2024-10-15)
==================================================================================================
This release fixes High severity vulnerability CVE-2024-47771 / GHSA-963w-49j9-gxj6

Changes in [1.11.80](https://github.com/element-hq/element-web/releases/tag/v1.11.80) (2024-10-08)
==================================================================================================
## ✨ Features
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "element-web",
"version": "1.11.80",
"version": "1.11.81",
"description": "A feature-rich client for Matrix.org",
"author": "New Vector Ltd.",
"repository": {
Expand Down Expand Up @@ -82,8 +82,8 @@
"jsrsasign": "^11.0.0",
"katex": "^0.16.0",
"lodash": "^4.17.21",
"matrix-js-sdk": "34.7.0",
"matrix-react-sdk": "3.112.0",
"matrix-js-sdk": "34.8.0",
"matrix-react-sdk": "3.113.0",
"matrix-widget-api": "^1.8.2",
"react": "17.0.2",
"react-dom": "17.0.2",
Expand Down
1 change: 1 addition & 0 deletions src/@types/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type ElectronChannel =
| "userDownloadAction"
| "openDesktopCapturerSourcePicker"
| "userAccessToken"
| "homeserverUrl"
| "serverSupportedVersions";

declare global {
Expand Down
48 changes: 30 additions & 18 deletions src/serviceworker/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,47 +40,58 @@ global.addEventListener("fetch", (event: FetchEvent) => {

// Note: ideally we'd keep the request headers etc, but in practice we can't even see those details.
// See https://stackoverflow.com/a/59152482
let url = event.request.url;
const url = new URL(event.request.url);

// We only intercept v3 download and thumbnail requests as presumably everything else is deliberate.
// For example, `/_matrix/media/unstable` or `/_matrix/media/v3/preview_url` are something well within
// the control of the application, and appear to be choices made at a higher level than us.
if (!url.includes("/_matrix/media/v3/download") && !url.includes("/_matrix/media/v3/thumbnail")) {
if (
!url.pathname.startsWith("/_matrix/media/v3/download") &&
!url.pathname.startsWith("/_matrix/media/v3/thumbnail")
) {
return; // not a URL we care about
}

// We need to call respondWith synchronously, otherwise we may never execute properly. This means
// later on we need to proxy the request through if it turns out the server doesn't support authentication.
event.respondWith(
(async (): Promise<Response> => {
let accessToken: string | undefined;
let auth: { accessToken?: string; homeserver: string } | undefined;
try {
// Figure out which homeserver we're communicating with
const csApi = url.substring(0, url.indexOf("/_matrix/media/v3"));
const csApi = url.origin;

// Add jitter to reduce request spam, particularly to `/versions` on initial page load
await new Promise<void>((resolve) => setTimeout(() => resolve(), Math.random() * 10));

// Locate our access token, and populate the fetchConfig with the authentication header.
// Locate the access token and homeserver url
// @ts-expect-error - service worker types are not available. See 'fetch' event handler.
const client = await global.clients.get(event.clientId);
accessToken = await getAccessToken(client);
auth = await getAuthData(client);

// Is this request actually going to the homeserver?
const isRequestToHomeServer = url.origin === new URL(auth.homeserver).origin;
if (!isRequestToHomeServer) {
throw new Error("Request appears to be for media endpoint but wrong homeserver!");
}

// Update or populate the server support map using a (usually) authenticated `/versions` call.
await tryUpdateServerSupportMap(csApi, accessToken);
await tryUpdateServerSupportMap(csApi, auth.accessToken);

// If we have server support (and a means of authentication), rewrite the URL to use MSC3916 endpoints.
if (serverSupportMap[csApi].supportsAuthedMedia && accessToken) {
url = url.replace(/\/media\/v3\/(.*)\//, "/client/v1/media/$1/");
if (serverSupportMap[csApi].supportsAuthedMedia && auth.accessToken) {
url.href = url.href.replace(/\/media\/v3\/(.*)\//, "/client/v1/media/$1/");
} // else by default we make no changes
} catch (err) {
// In case of some error, we stay safe by not adding the access-token to the request.
auth = undefined;
console.error("SW: Error in request rewrite.", err);
}

// Add authentication and send the request. We add authentication even if MSC3916 endpoints aren't
// being used to ensure patches like this work:
// https://github.com/matrix-org/synapse/commit/2390b66bf0ec3ff5ffb0c7333f3c9b239eeb92bb
return fetch(url, fetchConfigForToken(accessToken));
return fetch(url, fetchConfigForToken(auth?.accessToken));
})(),
);
});
Expand All @@ -106,35 +117,36 @@ async function tryUpdateServerSupportMap(clientApiUrl: string, accessToken?: str

// Ideally we'd use the `Client` interface for `client`, but since it's not available (see 'fetch' listener), we use
// unknown for now and force-cast it to something close enough later.
async function getAccessToken(client: unknown): Promise<string | undefined> {
async function getAuthData(client: unknown): Promise<{ accessToken: string; homeserver: string }> {
// Access tokens are encrypted at rest, so while we can grab the "access token", we'll need to do work to get the
// real thing.
const encryptedAccessToken = await idbLoad("account", "mx_access_token");

// We need to extract a user ID and device ID from localstorage, which means calling WebPlatform for the
// read operation. Service workers can't access localstorage.
const { userId, deviceId } = await askClientForUserIdParams(client);
const { userId, deviceId, homeserver } = await askClientForUserIdParams(client);

// ... and this is why we need the user ID and device ID: they're index keys for the pickle key table.
const pickleKeyData = await idbLoad("pickleKey", [userId, deviceId]);
if (pickleKeyData && (!pickleKeyData.encrypted || !pickleKeyData.iv || !pickleKeyData.cryptoKey)) {
console.error("SW: Invalid pickle key loaded - ignoring");
return undefined;
throw new Error("SW: Invalid pickle key loaded - ignoring");
}

// Finally, try decrypting the thing and return that. This may fail, but that's okay.
try {
const pickleKey = await buildAndEncodePickleKey(pickleKeyData, userId, deviceId);
return tryDecryptToken(pickleKey, encryptedAccessToken, ACCESS_TOKEN_IV);
const accessToken = await tryDecryptToken(pickleKey, encryptedAccessToken, ACCESS_TOKEN_IV);
return { accessToken, homeserver };
} catch (e) {
console.error("SW: Error decrypting access token.", e);
return undefined;
throw new Error("SW: Error decrypting access token.", { cause: e });
}
}

// Ideally we'd use the `Client` interface for `client`, but since it's not available (see 'fetch' listener), we use
// unknown for now and force-cast it to something close enough inside the function.
async function askClientForUserIdParams(client: unknown): Promise<{ userId: string; deviceId: string }> {
async function askClientForUserIdParams(
client: unknown,
): Promise<{ userId: string; deviceId: string; homeserver: string }> {
return new Promise((resolve, reject) => {
// Dev note: this uses postMessage, which is a highly insecure channel. postMessage is typically visible to other
// tabs, windows, browser extensions, etc, making it far from ideal for sharing sensitive information. This is
Expand Down
5 changes: 5 additions & 0 deletions src/vector/platform/ElectronPlatform.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ export default class ElectronPlatform extends VectorBasePlatform {
window.electron!.send("userAccessToken", MatrixClientPeg.get()?.getAccessToken());
});

// `homeserverUrl` (IPC) is requested by the main process. A reply is sent over the same channel.
window.electron.on("homeserverUrl", () => {
window.electron!.send("homeserverUrl", MatrixClientPeg.get()?.getHomeserverUrl());
});

// `serverSupportedVersions` is requested by the main process when it needs to know if the
// server supports a particular version. This is primarily used to detect authenticated media
// support. A reply is sent over the same channel.
Expand Down
3 changes: 3 additions & 0 deletions src/vector/platform/WebPlatform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { Action } from "matrix-react-sdk/src/dispatcher/actions";
import { CheckUpdatesPayload } from "matrix-react-sdk/src/dispatcher/payloads/CheckUpdatesPayload";
import UAParser from "ua-parser-js";
import { logger } from "matrix-js-sdk/src/logger";
import { MatrixClientPeg } from "matrix-react-sdk/src/MatrixClientPeg";

import VectorBasePlatform from "./VectorBasePlatform";
import { parseQs } from "../url_utils";
Expand Down Expand Up @@ -62,10 +63,12 @@ export default class WebPlatform extends VectorBasePlatform {
if (event.data?.["type"] === "userinfo" && event.data?.["responseKey"]) {
const userId = localStorage.getItem("mx_user_id");
const deviceId = localStorage.getItem("mx_device_id");
const homeserver = MatrixClientPeg.get()?.getHomeserverUrl();
event.source!.postMessage({
responseKey: event.data["responseKey"],
userId,
deviceId,
homeserver,
});
}
} catch (e) {
Expand Down
18 changes: 9 additions & 9 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8192,10 +8192,10 @@ [email protected]:
resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz#c8c38911e2cb29023b0bbac8d6f32e0de2c957dd"
integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==

matrix-js-sdk@34.7.0:
version "34.7.0"
resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-34.7.0.tgz#243e4eacbedd98a1096135a75765756cda910b7b"
integrity sha512-epauE/ZwksDyadm+0vg+g1keRUo600H/b1MzDZbaIrCY9fELzq3fIWctq9IxMQE/EEPe9jjLiNDooGJT5JJ2Ag==
matrix-js-sdk@34.8.0:
version "34.8.0"
resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-34.8.0.tgz#7ee18fb2ca22fb96d72a6720d0e80f9de12dd4c6"
integrity sha512-zHYPE+vb6PP/dnRa6X3ktXf34yQg/Pct9r5CSP263hA4tRg5sKEVQgnY7KDADaABTvNMb9h7BZ5Xm9vjXsowNQ==
dependencies:
"@babel/runtime" "^7.12.5"
"@matrix-org/matrix-sdk-crypto-wasm" "^9.0.0"
Expand All @@ -8220,10 +8220,10 @@ matrix-mock-request@^2.5.0:
dependencies:
expect "^28.1.0"

matrix-react-sdk@3.112.0:
version "3.112.0"
resolved "https://registry.yarnpkg.com/matrix-react-sdk/-/matrix-react-sdk-3.112.0.tgz#653929e27554289b14e289113d97c6189c2f3db2"
integrity sha512-S7rwFJuTGi1lHOHYh2QEmzhLWrubNh+4LQ4K3Bpf/Oco1Hh0IdabMidvFWkSy0fUcrZRKN04Acif7iLem3IhSw==
matrix-react-sdk@3.113.0:
version "3.113.0"
resolved "https://registry.yarnpkg.com/matrix-react-sdk/-/matrix-react-sdk-3.113.0.tgz#66bb447d6ead469df191a9cd1a412210b18a2c95"
integrity sha512-ZMOcmcCRW1La3dp9Cqyuuk9qUP+OLJ4BCB2zkjwpUbSKO35e1ahFy+dYqtRNFbCz3C2Auzw3ED9kV2uLFkTxLg==
dependencies:
"@babel/runtime" "^7.12.5"
"@matrix-org/analytics-events" "^0.25.0"
Expand Down Expand Up @@ -8268,7 +8268,7 @@ [email protected]:
maplibre-gl "^2.0.0"
matrix-encrypt-attachment "^1.0.3"
matrix-events-sdk "0.0.1"
matrix-js-sdk "34.7.0"
matrix-js-sdk "34.8.0"
matrix-widget-api "^1.9.0"
memoize-one "^6.0.0"
minimist "^1.2.5"
Expand Down

0 comments on commit 8cc88e6

Please sign in to comment.