diff --git a/package.json b/package.json index ba1c1231..1cb5fb70 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "i18next-http-backend": "^2.0.0", "livekit-client": "^1.12.3", "lodash": "^4.17.21", - "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#bf81c4bfebd52532d67d30a66e651e3658c8aaad", + "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#4ce837b20e638a185f9002b2388fbaf48975ee6e", "matrix-widget-api": "^1.3.1", "normalize.css": "^8.0.1", "pako": "^2.0.4", diff --git a/src/UrlParams.ts b/src/UrlParams.ts index ea1da7d6..c80b41e1 100644 --- a/src/UrlParams.ts +++ b/src/UrlParams.ts @@ -115,10 +115,6 @@ interface UrlParams { * E2EE password */ password: string | null; - /** - * Whether we the app should use per participant keys for E2EE. - */ - perParticipantE2EE: boolean; /** * Setting this flag skips the lobby and brings you in the call directly. * In the widget this can be combined with preload to pass the device settings @@ -221,7 +217,6 @@ export const getUrlParams = ( fontScale: Number.isNaN(fontScale) ? null : fontScale, analyticsID: parser.getParam("analyticsID"), allowIceFallback: parser.getFlagParam("allowIceFallback"), - perParticipantE2EE: parser.getFlagParam("perParticipantE2EE"), skipLobby: parser.getFlagParam("skipLobby"), }; }; diff --git a/src/e2ee/e2eeType.ts b/src/e2ee/e2eeType.ts deleted file mode 100644 index be4a4ba9..00000000 --- a/src/e2ee/e2eeType.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* -Copyright 2023 New Vector Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -export enum E2eeType { - NONE = 0, - PER_PARTICIPANT = 1, - SHARED_KEY = 2, -} diff --git a/src/e2ee/matrixKeyProvider.ts b/src/e2ee/matrixKeyProvider.ts deleted file mode 100644 index 7fac8193..00000000 --- a/src/e2ee/matrixKeyProvider.ts +++ /dev/null @@ -1,73 +0,0 @@ -/* -Copyright 2023 New Vector Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import { BaseKeyProvider, createKeyMaterialFromBuffer } from "livekit-client"; -import { logger } from "matrix-js-sdk/src/logger"; -import { - MatrixRTCSession, - MatrixRTCSessionEvent, -} from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; - -export class MatrixKeyProvider extends BaseKeyProvider { - private rtcSession?: MatrixRTCSession; - - public constructor() { - super({ ratchetWindowSize: 0 }); - } - - public setRTCSession(rtcSession: MatrixRTCSession): void { - if (this.rtcSession) { - this.rtcSession.off( - MatrixRTCSessionEvent.EncryptionKeyChanged, - this.onEncryptionKeyChanged, - ); - } - - this.rtcSession = rtcSession; - - this.rtcSession.on( - MatrixRTCSessionEvent.EncryptionKeyChanged, - this.onEncryptionKeyChanged, - ); - - // The new session could be aware of keys of which the old session wasn't, - // so emit a key changed event. - for (const [ - participant, - encryptionKeys, - ] of this.rtcSession.getEncryptionKeys()) { - for (const [index, encryptionKey] of encryptionKeys.entries()) { - this.onEncryptionKeyChanged(encryptionKey, index, participant); - } - } - } - - private onEncryptionKeyChanged = async ( - encryptionKey: Uint8Array, - encryptionKeyIndex: number, - participantId: string, - ): Promise => { - this.onSetEncryptionKey( - await createKeyMaterialFromBuffer(encryptionKey), - participantId, - encryptionKeyIndex, - ); - - logger.debug( - `Sent new key to livekit room=${this.rtcSession?.room.roomId} participantId=${participantId} encryptionKeyIndex=${encryptionKeyIndex}`, - ); - }; -} diff --git a/src/home/RegisteredView.tsx b/src/home/RegisteredView.tsx index 4da46696..c5ff8cfa 100644 --- a/src/home/RegisteredView.tsx +++ b/src/home/RegisteredView.tsx @@ -40,7 +40,6 @@ import { Caption } from "../typography/Typography"; import { Form } from "../form/Form"; import { useOptInAnalytics } from "../settings/useSetting"; import { AnalyticsNotice } from "../analytics/AnalyticsNotice"; -import { E2eeType } from "../e2ee/e2eeType"; interface Props { client: MatrixClient; @@ -73,11 +72,7 @@ export const RegisteredView: FC = ({ client }) => { setError(undefined); setLoading(true); - const createRoomResult = await createRoom( - client, - roomName, - E2eeType.SHARED_KEY, - ); + const createRoomResult = await createRoom(client, roomName, true); history.push( getRelativeRoomUrl( diff --git a/src/home/UnauthenticatedView.tsx b/src/home/UnauthenticatedView.tsx index 732f0633..91e8b56e 100644 --- a/src/home/UnauthenticatedView.tsx +++ b/src/home/UnauthenticatedView.tsx @@ -43,7 +43,6 @@ import { generateRandomName } from "../auth/generateRandomName"; import { AnalyticsNotice } from "../analytics/AnalyticsNotice"; import { useOptInAnalytics } from "../settings/useSetting"; import { Config } from "../config/Config"; -import { E2eeType } from "../e2ee/e2eeType"; export const UnauthenticatedView: FC = () => { const { setClient } = useClient(); @@ -85,11 +84,7 @@ export const UnauthenticatedView: FC = () => { let createRoomResult; try { - createRoomResult = await createRoom( - client, - roomName, - E2eeType.SHARED_KEY, - ); + createRoomResult = await createRoom(client, roomName, true); } catch (error) { if (!setClient) { throw error; diff --git a/src/livekit/useLiveKit.ts b/src/livekit/useLiveKit.ts index 3e6f6397..43a4703e 100644 --- a/src/livekit/useLiveKit.ts +++ b/src/livekit/useLiveKit.ts @@ -26,7 +26,6 @@ import { useLiveKitRoom } from "@livekit/components-react"; import { useEffect, useMemo, useRef, useState } from "react"; import E2EEWorker from "livekit-client/e2ee-worker?worker"; import { logger } from "matrix-js-sdk/src/logger"; -import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; import { defaultLiveKitOptions } from "./options"; import { SFUConfig } from "./openIDSFU"; @@ -40,12 +39,9 @@ import { ECConnectionState, useECConnectionState, } from "./useECConnectionState"; -import { MatrixKeyProvider } from "../e2ee/matrixKeyProvider"; -import { E2eeType } from "../e2ee/e2eeType"; export type E2EEConfig = { - mode: E2eeType; - sharedKey?: string; + sharedKey: string; }; interface UseLivekitResult { @@ -54,44 +50,26 @@ interface UseLivekitResult { } export function useLiveKit( - rtcSession: MatrixRTCSession, muteStates: MuteStates, sfuConfig?: SFUConfig, e2eeConfig?: E2EEConfig, ): UseLivekitResult { - const e2eeOptions = useMemo((): E2EEOptions | undefined => { - if (!e2eeConfig || e2eeConfig.mode === E2eeType.NONE) return undefined; + const e2eeOptions = useMemo(() => { + if (!e2eeConfig?.sharedKey) return undefined; - if (e2eeConfig.mode === E2eeType.PER_PARTICIPANT) { - return { - keyProvider: new MatrixKeyProvider(), - worker: new E2EEWorker(), - }; - } else if ( - e2eeConfig.mode === E2eeType.SHARED_KEY && - e2eeConfig.sharedKey - ) { - return { - keyProvider: new ExternalE2EEKeyProvider(), - worker: new E2EEWorker(), - }; - } + return { + keyProvider: new ExternalE2EEKeyProvider(), + worker: new E2EEWorker(), + } as E2EEOptions; }, [e2eeConfig]); useEffect(() => { - if (!e2eeConfig || !e2eeOptions) return; + if (!e2eeConfig?.sharedKey || !e2eeOptions) return; - if (e2eeConfig.mode === E2eeType.PER_PARTICIPANT) { - (e2eeOptions.keyProvider as MatrixKeyProvider).setRTCSession(rtcSession); - } else if ( - e2eeConfig.mode === E2eeType.SHARED_KEY && - e2eeConfig.sharedKey - ) { - (e2eeOptions.keyProvider as ExternalE2EEKeyProvider).setKey( - e2eeConfig.sharedKey, - ); - } - }, [e2eeOptions, e2eeConfig, rtcSession]); + (e2eeOptions.keyProvider as ExternalE2EEKeyProvider).setKey( + e2eeConfig?.sharedKey, + ); + }, [e2eeOptions, e2eeConfig?.sharedKey]); const initialMuteStates = useRef(muteStates); const devices = useMediaDevices(); diff --git a/src/matrix-utils.ts b/src/matrix-utils.ts index 90fdc705..52c55959 100644 --- a/src/matrix-utils.ts +++ b/src/matrix-utils.ts @@ -28,7 +28,6 @@ import { GroupCallIntent, GroupCallType, } from "matrix-js-sdk/src/webrtc/groupCall"; -import { secureRandomBase64Url } from "matrix-js-sdk/src/randomstring"; import type { MatrixClient } from "matrix-js-sdk/src/client"; import type { Room } from "matrix-js-sdk/src/models/room"; @@ -38,7 +37,6 @@ import { loadOlm } from "./olm"; import { Config } from "./config/Config"; import { setLocalStorageItem } from "./useLocalStorage"; import { getRoomSharedKeyLocalStorageKey } from "./e2ee/sharedKeyManagement"; -import { E2eeType } from "./e2ee/e2eeType"; export const fallbackICEServerAllowed = import.meta.env.VITE_FALLBACK_STUN_ALLOWED === "true"; @@ -75,6 +73,23 @@ function waitForSync(client: MatrixClient): Promise { }); } +function secureRandomString(entropyBytes: number): string { + const key = new Uint8Array(entropyBytes); + crypto.getRandomValues(key); + // encode to base64url as this value goes into URLs + // base64url is just base64 with thw two non-alphanum characters swapped out for + // ones that can be put in a URL without encoding. Browser JS has a native impl + // for base64 encoding but only a string (there isn't one that takes a UInt8Array + // yet) so just use the built-in one and convert, replace the chars and strip the + // padding from the end (otherwise we'd need to pull in another dependency). + return btoa( + key.reduce((acc, current) => acc + String.fromCharCode(current), ""), + ) + .replace("+", "-") + .replace("/", "_") + .replace(/=*$/, ""); +} + /** * Initialises and returns a new standalone Matrix Client. * If true is passed for the 'restore' parameter, a check will be made @@ -279,20 +294,10 @@ interface CreateRoomResult { password?: string; } -/** - * Create a new room ready for calls - * - * @param client Matrix client to use - * @param name The name of the room - * @param e2ee The type of e2ee call to create. Note that we would currently never - * create a room for per-participant e2ee calls: since it's used in - * embedded mode, we use the existing room. - * @returns Object holding information about the new room - */ export async function createRoom( client: MatrixClient, name: string, - e2ee: E2eeType, + e2ee: boolean, ): Promise { logger.log(`Creating room for group call`); const createPromise = client.createRoom({ @@ -357,8 +362,8 @@ export async function createRoom( ); let password; - if (e2ee == E2eeType.SHARED_KEY) { - password = secureRandomBase64Url(16); + if (e2ee) { + password = secureRandomString(16); setLocalStorageItem( getRoomSharedKeyLocalStorageKey(result.room_id), password, diff --git a/src/room/GroupCallView.tsx b/src/room/GroupCallView.tsx index 69f466f2..6466a3c6 100644 --- a/src/room/GroupCallView.tsx +++ b/src/room/GroupCallView.tsx @@ -44,9 +44,6 @@ import { useRoomAvatar } from "./useRoomAvatar"; import { useRoomName } from "./useRoomName"; import { useJoinRule } from "./useJoinRule"; import { InviteModal } from "./InviteModal"; -import { E2EEConfig } from "../livekit/useLiveKit"; -import { useUrlParams } from "../UrlParams"; -import { E2eeType } from "../e2ee/e2eeType"; declare global { interface Window { @@ -90,7 +87,6 @@ export const GroupCallView: FC = ({ const roomName = useRoomName(rtcSession.room); const roomAvatar = useRoomAvatar(rtcSession.room); const roomEncrypted = useIsRoomE2EE(rtcSession.room.roomId)!; - const { perParticipantE2EE } = useUrlParams(); const matrixInfo = useMemo((): MatrixInfo => { return { @@ -186,7 +182,7 @@ export const GroupCallView: FC = ({ ev: CustomEvent, ): Promise => { defaultDeviceSetup(ev.detail.data as unknown as JoinCallData); - enterRTCSession(rtcSession, perParticipantE2EE); + enterRTCSession(rtcSession); await Promise.all([ widget!.api.setAlwaysOnScreen(true), widget!.api.transport.reply(ev.detail, {}), @@ -199,9 +195,9 @@ export const GroupCallView: FC = ({ } else { // if we don't use preload and only skipLobby we enter the rtc session right away defaultDeviceSetup({ audioInput: null, videoInput: null }); - enterRTCSession(rtcSession, perParticipantE2EE); + enterRTCSession(rtcSession); } - }, [rtcSession, preload, skipLobby, perParticipantE2EE]); + }, [rtcSession, preload, skipLobby]); const [left, setLeft] = useState(false); const [leaveError, setLeaveError] = useState(undefined); @@ -249,19 +245,16 @@ export const GroupCallView: FC = ({ } }, [isJoined, rtcSession]); - const e2eeConfig = useMemo((): E2EEConfig | undefined => { - if (perParticipantE2EE) { - return { mode: E2eeType.PER_PARTICIPANT }; - } else if (e2eeSharedKey) { - return { mode: E2eeType.SHARED_KEY, sharedKey: e2eeSharedKey }; - } - }, [perParticipantE2EE, e2eeSharedKey]); + const e2eeConfig = useMemo( + () => (e2eeSharedKey ? { sharedKey: e2eeSharedKey } : undefined), + [e2eeSharedKey], + ); const onReconnect = useCallback(() => { setLeft(false); setLeaveError(undefined); - enterRTCSession(rtcSession, perParticipantE2EE); - }, [rtcSession, perParticipantE2EE]); + enterRTCSession(rtcSession); + }, [rtcSession]); const joinRule = useJoinRule(rtcSession.room); @@ -287,7 +280,7 @@ export const GroupCallView: FC = ({ const { t } = useTranslation(); - if (isRoomE2EE && !perParticipantE2EE && !e2eeSharedKey) { + if (isRoomE2EE && !e2eeSharedKey) { return ( = ({ client={client} matrixInfo={matrixInfo} muteStates={muteStates} - onEnter={(): void => enterRTCSession(rtcSession, perParticipantE2EE)} + onEnter={(): void => enterRTCSession(rtcSession)} confineToRoom={confineToRoom} hideHeader={hideHeader} participantCount={participantCount} diff --git a/src/room/InCallView.tsx b/src/room/InCallView.tsx index ac0bbf9b..559f01e7 100644 --- a/src/room/InCallView.tsx +++ b/src/room/InCallView.tsx @@ -101,7 +101,6 @@ export interface ActiveCallProps export const ActiveCall: FC = (props) => { const sfuConfig = useOpenIDSFU(props.client, props.rtcSession); const { livekitRoom, connState } = useLiveKit( - props.rtcSession, props.muteStates, sfuConfig, props.e2eeConfig, diff --git a/src/rtcSessionHelpers.ts b/src/rtcSessionHelpers.ts index 2291e80c..80978098 100644 --- a/src/rtcSessionHelpers.ts +++ b/src/rtcSessionHelpers.ts @@ -34,10 +34,7 @@ function makeFocus(livekitAlias: string): LivekitFocus { }; } -export function enterRTCSession( - rtcSession: MatrixRTCSession, - encryptMedia: boolean, -): void { +export function enterRTCSession(rtcSession: MatrixRTCSession): void { PosthogAnalytics.instance.eventCallEnded.cacheStartCall(new Date()); PosthogAnalytics.instance.eventCallStarted.track(rtcSession.room.roomId); @@ -48,7 +45,7 @@ export function enterRTCSession( // right now we assume everything is a room-scoped call const livekitAlias = rtcSession.room.roomId; - rtcSession.joinRoomSession([makeFocus(livekitAlias)], encryptMedia); + rtcSession.joinRoomSession([makeFocus(livekitAlias)]); } const widgetPostHangupProcedure = async ( diff --git a/src/widget.ts b/src/widget.ts index 1ca6a2e3..7a8e03f8 100644 --- a/src/widget.ts +++ b/src/widget.ts @@ -77,8 +77,6 @@ export const widget = ((): WidgetHelpers | null => { logger.info("Widget API is available"); const api = new WidgetApi(widgetId, parentOrigin); api.requestCapability(MatrixCapabilities.AlwaysOnScreen); - api.requestCapabilityToSendEvent(EventType.CallEncryptionKeysPrefix); - api.requestCapabilityToReceiveEvent(EventType.CallEncryptionKeysPrefix); // Set up the lazy action emitter, but only for select actions that we // intend for the app to handle diff --git a/yarn.lock b/yarn.lock index 55af6a1c..b75d160f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2010,10 +2010,10 @@ clsx "^2.0.0" usehooks-ts "^2.9.1" -"@matrix-org/matrix-sdk-crypto-wasm@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-2.2.0.tgz#7c60afe01915281a6b71502821bc8e01afbfa70d" - integrity sha512-txmvaTiZpVV0/kWCRcE7tZvRESCEc1ynLJDVh9OUsFlaXfl13c7qdD3E6IJEJ8YiPMIn+PHogdfBZsO84reaMg== +"@matrix-org/matrix-sdk-crypto-wasm@^1.2.3-alpha.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-1.3.0.tgz#f098c72701801334eeb7049ca7074fe6eb3686d6" + integrity sha512-vQ5PVppKu1PY7xy7QDw+RJLYLGFKhJyxLqjXHr0uEUJwfvz2IH2njTLXzrz77dOo9qacxJ9/YNOTe0Hl+98N0A== "@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz": version "3.2.14" @@ -7101,12 +7101,12 @@ matrix-events-sdk@0.0.1: resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz#c8c38911e2cb29023b0bbac8d6f32e0de2c957dd" integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA== -"matrix-js-sdk@github:matrix-org/matrix-js-sdk#bf81c4bfebd52532d67d30a66e651e3658c8aaad": - version "29.1.0" - resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/bf81c4bfebd52532d67d30a66e651e3658c8aaad" +"matrix-js-sdk@github:matrix-org/matrix-js-sdk#4ce837b20e638a185f9002b2388fbaf48975ee6e": + version "29.0.0" + resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/4ce837b20e638a185f9002b2388fbaf48975ee6e" dependencies: "@babel/runtime" "^7.12.5" - "@matrix-org/matrix-sdk-crypto-wasm" "^2.2.0" + "@matrix-org/matrix-sdk-crypto-wasm" "^1.2.3-alpha.0" another-json "^0.2.0" bs58 "^5.0.0" content-type "^1.0.4"