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:
David Baker
2023-10-16 17:45:06 +01:00
parent cb39e760ab
commit 15d3e7574d
7 changed files with 130 additions and 23 deletions

View File

@@ -111,6 +111,10 @@ interface UrlParams {
* E2EE password
*/
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
@@ -206,6 +210,7 @@ export const getUrlParams = (
fontScale: Number.isNaN(fontScale) ? null : fontScale,
analyticsID: parser.getParam("analyticsID"),
allowIceFallback: parser.getFlagParam("allowIceFallback"),
perParticipantE2EE: true /*parser.getFlagParam("perParticipantE2EE")*/,
};
};

View 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(),
);
};
}

View File

@@ -26,6 +26,7 @@ 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";
@@ -39,9 +40,16 @@ import {
ECConnectionState,
useECConnectionState,
} from "./useECConnectionState";
import { MatrixKeyProvider } from "../e2ee/matrixKeyProvider";
export enum E2EEMode {
PerParticipantKey = "per_participant_key",
SharedKey = "shared_key",
}
export type E2EEConfig = {
sharedKey: string;
mode: E2EEMode;
sharedKey?: string;
};
interface UseLivekitResult {
@@ -50,26 +58,38 @@ interface UseLivekitResult {
}
export function useLiveKit(
rtcSession: MatrixRTCSession,
muteStates: MuteStates,
sfuConfig?: SFUConfig,
e2eeConfig?: E2EEConfig,
): UseLivekitResult {
const e2eeOptions = useMemo(() => {
if (!e2eeConfig?.sharedKey) return undefined;
const e2eeOptions = useMemo((): E2EEOptions | undefined => {
if (!e2eeConfig) return undefined;
return {
keyProvider: new ExternalE2EEKeyProvider(),
worker: new E2EEWorker(),
} as E2EEOptions;
if (e2eeConfig.mode === E2EEMode.PerParticipantKey) {
return {
keyProvider: new MatrixKeyProvider(),
worker: new E2EEWorker(),
};
} else if (e2eeConfig.mode === E2EEMode.SharedKey && e2eeConfig.sharedKey) {
return {
keyProvider: new ExternalE2EEKeyProvider(),
worker: new E2EEWorker(),
};
}
}, [e2eeConfig]);
useEffect(() => {
if (!e2eeConfig?.sharedKey || !e2eeOptions) return;
if (!e2eeConfig || !e2eeOptions) return;
(e2eeOptions.keyProvider as ExternalE2EEKeyProvider).setKey(
e2eeConfig?.sharedKey,
);
}, [e2eeOptions, e2eeConfig?.sharedKey]);
if (e2eeConfig.mode === E2EEMode.PerParticipantKey) {
(e2eeOptions.keyProvider as MatrixKeyProvider).setRTCSession(rtcSession);
} else if (e2eeConfig.mode === E2EEMode.SharedKey && e2eeConfig.sharedKey) {
(e2eeOptions.keyProvider as ExternalE2EEKeyProvider).setKey(
e2eeConfig.sharedKey,
);
}
}, [e2eeOptions, e2eeConfig, rtcSession]);
const initialMuteStates = useRef<MuteStates>(muteStates);
const devices = useMediaDevices();

View File

@@ -44,6 +44,8 @@ import { useRoomAvatar } from "./useRoomAvatar";
import { useRoomName } from "./useRoomName";
import { useJoinRule } from "./useJoinRule";
import { InviteModal } from "./InviteModal";
import { E2EEConfig, E2EEMode } from "../livekit/useLiveKit";
import { useUrlParams } from "../UrlParams";
declare global {
interface Window {
@@ -85,6 +87,7 @@ export const GroupCallView: FC<Props> = ({
const roomName = useRoomName(rtcSession.room);
const roomAvatar = useRoomAvatar(rtcSession.room);
const roomEncrypted = useIsRoomE2EE(rtcSession.room.roomId)!;
const { perParticipantE2EE } = useUrlParams();
const matrixInfo = useMemo((): MatrixInfo => {
return {
@@ -176,7 +179,7 @@ export const GroupCallView: FC<Props> = ({
}
}
enterRTCSession(rtcSession);
enterRTCSession(rtcSession, perParticipantE2EE);
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
@@ -195,7 +198,7 @@ export const GroupCallView: FC<Props> = ({
widget!.lazyActions.off(ElementWidgetActions.JoinCall, onJoin);
};
}
}, [rtcSession, preload]);
}, [rtcSession, preload, perParticipantE2EE]);
const [left, setLeft] = useState(false);
const [leaveError, setLeaveError] = useState<Error | undefined>(undefined);
@@ -255,16 +258,19 @@ export const GroupCallView: FC<Props> = ({
}
}, [isJoined, rtcSession]);
const e2eeConfig = useMemo(
() => (e2eeSharedKey ? { sharedKey: e2eeSharedKey } : undefined),
[e2eeSharedKey],
);
const e2eeConfig = useMemo((): E2EEConfig | undefined => {
if (perParticipantE2EE) {
return { mode: E2EEMode.PerParticipantKey };
} else if (e2eeSharedKey) {
return { mode: E2EEMode.SharedKey, sharedKey: e2eeSharedKey };
}
}, [perParticipantE2EE, e2eeSharedKey]);
const onReconnect = useCallback(() => {
setLeft(false);
setLeaveError(undefined);
enterRTCSession(rtcSession);
}, [rtcSession]);
enterRTCSession(rtcSession, perParticipantE2EE);
}, [rtcSession, perParticipantE2EE]);
const joinRule = useJoinRule(rtcSession.room);
@@ -380,7 +386,7 @@ export const GroupCallView: FC<Props> = ({
client={client}
matrixInfo={matrixInfo}
muteStates={muteStates}
onEnter={(): void => enterRTCSession(rtcSession)}
onEnter={(): void => enterRTCSession(rtcSession, perParticipantE2EE)}
confineToRoom={confineToRoom}
hideHeader={hideHeader}
participantCount={participantCount}

View File

@@ -100,6 +100,7 @@ export interface ActiveCallProps
export const ActiveCall: FC<ActiveCallProps> = (props) => {
const sfuConfig = useOpenIDSFU(props.client, props.rtcSession);
const { livekitRoom, connState } = useLiveKit(
props.rtcSession,
props.muteStates,
sfuConfig,
props.e2eeConfig,

View File

@@ -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.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
const livekitAlias = rtcSession.room.roomId;
rtcSession.joinRoomSession([makeFocus(livekitAlias)]);
rtcSession.joinRoomSession([makeFocus(livekitAlias)], encryptMedia);
}
export async function leaveRTCSession(

View File

@@ -77,6 +77,8 @@ export const widget = ((): WidgetHelpers | null => {
logger.info("Widget API is available");
const api = new WidgetApi(widgetId, parentOrigin);
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
// intend for the app to handle