Re-apply Simon's emebdded e2ee work on latest livekit branch
Replaces https://github.com/vector-im/element-call/pull/1350
This commit is contained in:
@@ -111,6 +111,10 @@ interface UrlParams {
|
|||||||
* E2EE password
|
* E2EE password
|
||||||
*/
|
*/
|
||||||
password: string | null;
|
password: string | null;
|
||||||
|
/**
|
||||||
|
* Whether we the app should use per participant keys for E2EE.
|
||||||
|
*/
|
||||||
|
perParticipantE2EE: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is here as a stopgap, but what would be far nicer is a function that
|
// This is here as a stopgap, but what would be far nicer is a function that
|
||||||
@@ -206,6 +210,7 @@ export const getUrlParams = (
|
|||||||
fontScale: Number.isNaN(fontScale) ? null : fontScale,
|
fontScale: Number.isNaN(fontScale) ? null : fontScale,
|
||||||
analyticsID: parser.getParam("analyticsID"),
|
analyticsID: parser.getParam("analyticsID"),
|
||||||
allowIceFallback: parser.getFlagParam("allowIceFallback"),
|
allowIceFallback: parser.getFlagParam("allowIceFallback"),
|
||||||
|
perParticipantE2EE: true /*parser.getFlagParam("perParticipantE2EE")*/,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
70
src/e2ee/matrixKeyProvider.ts
Normal file
70
src/e2ee/matrixKeyProvider.ts
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
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, createKeyMaterialFromString } 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 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: string,
|
||||||
|
encryptionKeyIndex: number,
|
||||||
|
participantId: string,
|
||||||
|
): Promise<void> => {
|
||||||
|
this.onSetEncryptionKey(
|
||||||
|
await createKeyMaterialFromString(encryptionKey),
|
||||||
|
participantId,
|
||||||
|
encryptionKeyIndex,
|
||||||
|
);
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
`Embedded-E2EE-LOG onEncryptionKeyChanged participantId=${participantId} encryptionKeyIndex=${encryptionKeyIndex} encryptionKey=${encryptionKey}`,
|
||||||
|
this.getKeys(),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -26,6 +26,7 @@ import { useLiveKitRoom } from "@livekit/components-react";
|
|||||||
import { useEffect, useMemo, useRef, useState } from "react";
|
import { useEffect, useMemo, useRef, useState } from "react";
|
||||||
import E2EEWorker from "livekit-client/e2ee-worker?worker";
|
import E2EEWorker from "livekit-client/e2ee-worker?worker";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
||||||
|
|
||||||
import { defaultLiveKitOptions } from "./options";
|
import { defaultLiveKitOptions } from "./options";
|
||||||
import { SFUConfig } from "./openIDSFU";
|
import { SFUConfig } from "./openIDSFU";
|
||||||
@@ -39,9 +40,16 @@ import {
|
|||||||
ECConnectionState,
|
ECConnectionState,
|
||||||
useECConnectionState,
|
useECConnectionState,
|
||||||
} from "./useECConnectionState";
|
} from "./useECConnectionState";
|
||||||
|
import { MatrixKeyProvider } from "../e2ee/matrixKeyProvider";
|
||||||
|
|
||||||
|
export enum E2EEMode {
|
||||||
|
PerParticipantKey = "per_participant_key",
|
||||||
|
SharedKey = "shared_key",
|
||||||
|
}
|
||||||
|
|
||||||
export type E2EEConfig = {
|
export type E2EEConfig = {
|
||||||
sharedKey: string;
|
mode: E2EEMode;
|
||||||
|
sharedKey?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface UseLivekitResult {
|
interface UseLivekitResult {
|
||||||
@@ -50,26 +58,38 @@ interface UseLivekitResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useLiveKit(
|
export function useLiveKit(
|
||||||
|
rtcSession: MatrixRTCSession,
|
||||||
muteStates: MuteStates,
|
muteStates: MuteStates,
|
||||||
sfuConfig?: SFUConfig,
|
sfuConfig?: SFUConfig,
|
||||||
e2eeConfig?: E2EEConfig,
|
e2eeConfig?: E2EEConfig,
|
||||||
): UseLivekitResult {
|
): UseLivekitResult {
|
||||||
const e2eeOptions = useMemo(() => {
|
const e2eeOptions = useMemo((): E2EEOptions | undefined => {
|
||||||
if (!e2eeConfig?.sharedKey) return undefined;
|
if (!e2eeConfig) return undefined;
|
||||||
|
|
||||||
return {
|
if (e2eeConfig.mode === E2EEMode.PerParticipantKey) {
|
||||||
keyProvider: new ExternalE2EEKeyProvider(),
|
return {
|
||||||
worker: new E2EEWorker(),
|
keyProvider: new MatrixKeyProvider(),
|
||||||
} as E2EEOptions;
|
worker: new E2EEWorker(),
|
||||||
|
};
|
||||||
|
} else if (e2eeConfig.mode === E2EEMode.SharedKey && e2eeConfig.sharedKey) {
|
||||||
|
return {
|
||||||
|
keyProvider: new ExternalE2EEKeyProvider(),
|
||||||
|
worker: new E2EEWorker(),
|
||||||
|
};
|
||||||
|
}
|
||||||
}, [e2eeConfig]);
|
}, [e2eeConfig]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!e2eeConfig?.sharedKey || !e2eeOptions) return;
|
if (!e2eeConfig || !e2eeOptions) return;
|
||||||
|
|
||||||
(e2eeOptions.keyProvider as ExternalE2EEKeyProvider).setKey(
|
if (e2eeConfig.mode === E2EEMode.PerParticipantKey) {
|
||||||
e2eeConfig?.sharedKey,
|
(e2eeOptions.keyProvider as MatrixKeyProvider).setRTCSession(rtcSession);
|
||||||
);
|
} else if (e2eeConfig.mode === E2EEMode.SharedKey && e2eeConfig.sharedKey) {
|
||||||
}, [e2eeOptions, e2eeConfig?.sharedKey]);
|
(e2eeOptions.keyProvider as ExternalE2EEKeyProvider).setKey(
|
||||||
|
e2eeConfig.sharedKey,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [e2eeOptions, e2eeConfig, rtcSession]);
|
||||||
|
|
||||||
const initialMuteStates = useRef<MuteStates>(muteStates);
|
const initialMuteStates = useRef<MuteStates>(muteStates);
|
||||||
const devices = useMediaDevices();
|
const devices = useMediaDevices();
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ import { useRoomAvatar } from "./useRoomAvatar";
|
|||||||
import { useRoomName } from "./useRoomName";
|
import { useRoomName } from "./useRoomName";
|
||||||
import { useJoinRule } from "./useJoinRule";
|
import { useJoinRule } from "./useJoinRule";
|
||||||
import { InviteModal } from "./InviteModal";
|
import { InviteModal } from "./InviteModal";
|
||||||
|
import { E2EEConfig, E2EEMode } from "../livekit/useLiveKit";
|
||||||
|
import { useUrlParams } from "../UrlParams";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
@@ -85,6 +87,7 @@ export const GroupCallView: FC<Props> = ({
|
|||||||
const roomName = useRoomName(rtcSession.room);
|
const roomName = useRoomName(rtcSession.room);
|
||||||
const roomAvatar = useRoomAvatar(rtcSession.room);
|
const roomAvatar = useRoomAvatar(rtcSession.room);
|
||||||
const roomEncrypted = useIsRoomE2EE(rtcSession.room.roomId)!;
|
const roomEncrypted = useIsRoomE2EE(rtcSession.room.roomId)!;
|
||||||
|
const { perParticipantE2EE } = useUrlParams();
|
||||||
|
|
||||||
const matrixInfo = useMemo((): MatrixInfo => {
|
const matrixInfo = useMemo((): MatrixInfo => {
|
||||||
return {
|
return {
|
||||||
@@ -176,7 +179,7 @@ export const GroupCallView: FC<Props> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enterRTCSession(rtcSession);
|
enterRTCSession(rtcSession, perParticipantE2EE);
|
||||||
|
|
||||||
PosthogAnalytics.instance.eventCallEnded.cacheStartCall(new Date());
|
PosthogAnalytics.instance.eventCallEnded.cacheStartCall(new Date());
|
||||||
// we only have room sessions right now, so call ID is the emprty string - we use the room ID
|
// we only have room sessions right now, so call ID is the emprty string - we use the room ID
|
||||||
@@ -195,7 +198,7 @@ export const GroupCallView: FC<Props> = ({
|
|||||||
widget!.lazyActions.off(ElementWidgetActions.JoinCall, onJoin);
|
widget!.lazyActions.off(ElementWidgetActions.JoinCall, onJoin);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}, [rtcSession, preload]);
|
}, [rtcSession, preload, perParticipantE2EE]);
|
||||||
|
|
||||||
const [left, setLeft] = useState(false);
|
const [left, setLeft] = useState(false);
|
||||||
const [leaveError, setLeaveError] = useState<Error | undefined>(undefined);
|
const [leaveError, setLeaveError] = useState<Error | undefined>(undefined);
|
||||||
@@ -255,16 +258,19 @@ export const GroupCallView: FC<Props> = ({
|
|||||||
}
|
}
|
||||||
}, [isJoined, rtcSession]);
|
}, [isJoined, rtcSession]);
|
||||||
|
|
||||||
const e2eeConfig = useMemo(
|
const e2eeConfig = useMemo((): E2EEConfig | undefined => {
|
||||||
() => (e2eeSharedKey ? { sharedKey: e2eeSharedKey } : undefined),
|
if (perParticipantE2EE) {
|
||||||
[e2eeSharedKey],
|
return { mode: E2EEMode.PerParticipantKey };
|
||||||
);
|
} else if (e2eeSharedKey) {
|
||||||
|
return { mode: E2EEMode.SharedKey, sharedKey: e2eeSharedKey };
|
||||||
|
}
|
||||||
|
}, [perParticipantE2EE, e2eeSharedKey]);
|
||||||
|
|
||||||
const onReconnect = useCallback(() => {
|
const onReconnect = useCallback(() => {
|
||||||
setLeft(false);
|
setLeft(false);
|
||||||
setLeaveError(undefined);
|
setLeaveError(undefined);
|
||||||
enterRTCSession(rtcSession);
|
enterRTCSession(rtcSession, perParticipantE2EE);
|
||||||
}, [rtcSession]);
|
}, [rtcSession, perParticipantE2EE]);
|
||||||
|
|
||||||
const joinRule = useJoinRule(rtcSession.room);
|
const joinRule = useJoinRule(rtcSession.room);
|
||||||
|
|
||||||
@@ -380,7 +386,7 @@ export const GroupCallView: FC<Props> = ({
|
|||||||
client={client}
|
client={client}
|
||||||
matrixInfo={matrixInfo}
|
matrixInfo={matrixInfo}
|
||||||
muteStates={muteStates}
|
muteStates={muteStates}
|
||||||
onEnter={(): void => enterRTCSession(rtcSession)}
|
onEnter={(): void => enterRTCSession(rtcSession, perParticipantE2EE)}
|
||||||
confineToRoom={confineToRoom}
|
confineToRoom={confineToRoom}
|
||||||
hideHeader={hideHeader}
|
hideHeader={hideHeader}
|
||||||
participantCount={participantCount}
|
participantCount={participantCount}
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ export interface ActiveCallProps
|
|||||||
export const ActiveCall: FC<ActiveCallProps> = (props) => {
|
export const ActiveCall: FC<ActiveCallProps> = (props) => {
|
||||||
const sfuConfig = useOpenIDSFU(props.client, props.rtcSession);
|
const sfuConfig = useOpenIDSFU(props.client, props.rtcSession);
|
||||||
const { livekitRoom, connState } = useLiveKit(
|
const { livekitRoom, connState } = useLiveKit(
|
||||||
|
props.rtcSession,
|
||||||
props.muteStates,
|
props.muteStates,
|
||||||
sfuConfig,
|
sfuConfig,
|
||||||
props.e2eeConfig,
|
props.e2eeConfig,
|
||||||
|
|||||||
@@ -33,7 +33,10 @@ function makeFocus(livekitAlias: string): LivekitFocus {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function enterRTCSession(rtcSession: MatrixRTCSession): void {
|
export function enterRTCSession(
|
||||||
|
rtcSession: MatrixRTCSession,
|
||||||
|
encryptMedia: boolean,
|
||||||
|
): void {
|
||||||
PosthogAnalytics.instance.eventCallEnded.cacheStartCall(new Date());
|
PosthogAnalytics.instance.eventCallEnded.cacheStartCall(new Date());
|
||||||
PosthogAnalytics.instance.eventCallStarted.track(rtcSession.room.roomId);
|
PosthogAnalytics.instance.eventCallStarted.track(rtcSession.room.roomId);
|
||||||
|
|
||||||
@@ -44,7 +47,7 @@ export function enterRTCSession(rtcSession: MatrixRTCSession): void {
|
|||||||
// right now we assume everything is a room-scoped call
|
// right now we assume everything is a room-scoped call
|
||||||
const livekitAlias = rtcSession.room.roomId;
|
const livekitAlias = rtcSession.room.roomId;
|
||||||
|
|
||||||
rtcSession.joinRoomSession([makeFocus(livekitAlias)]);
|
rtcSession.joinRoomSession([makeFocus(livekitAlias)], encryptMedia);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function leaveRTCSession(
|
export async function leaveRTCSession(
|
||||||
|
|||||||
@@ -77,6 +77,8 @@ export const widget = ((): WidgetHelpers | null => {
|
|||||||
logger.info("Widget API is available");
|
logger.info("Widget API is available");
|
||||||
const api = new WidgetApi(widgetId, parentOrigin);
|
const api = new WidgetApi(widgetId, parentOrigin);
|
||||||
api.requestCapability(MatrixCapabilities.AlwaysOnScreen);
|
api.requestCapability(MatrixCapabilities.AlwaysOnScreen);
|
||||||
|
api.requestCapabilityToSendEvent(EventType.CallEncryptionPrefix);
|
||||||
|
api.requestCapabilityToReceiveEvent(EventType.CallEncryptionPrefix);
|
||||||
|
|
||||||
// Set up the lazy action emitter, but only for select actions that we
|
// Set up the lazy action emitter, but only for select actions that we
|
||||||
// intend for the app to handle
|
// intend for the app to handle
|
||||||
|
|||||||
Reference in New Issue
Block a user