Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

E2EE for embeded mode #1350

Closed
wants to merge 28 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
fa5b014
E2EE for embeded mode
SimonBrandner Aug 22, 2023
b45c892
Handle embedding
SimonBrandner Aug 23, 2023
f279bd6
Remove unnecessary call
SimonBrandner Aug 24, 2023
127e8c9
`sharedKeyManagement.ts` -> `e2eeHooks.ts`
SimonBrandner Aug 28, 2023
b57454f
Change embedded E2EE implementation
SimonBrandner Aug 29, 2023
ee304fb
Update js-sdk
SimonBrandner Aug 25, 2023
1a7b7c9
Update-js sdk
SimonBrandner Aug 31, 2023
912ed55
Update js-sdk
SimonBrandner Sep 4, 2023
ae04cfc
Add a comment
SimonBrandner Sep 4, 2023
720e138
Merge remote-tracking branch 'upstream/dbkr/matrixrtcsession' into Si…
SimonBrandner Sep 4, 2023
2ec7557
Fix `useEnableSPAE2EE`
SimonBrandner Sep 4, 2023
5862f91
Fix map
SimonBrandner Sep 4, 2023
8516e52
Update js-sdk
SimonBrandner Sep 4, 2023
18185be
Update js-sdk
SimonBrandner Sep 4, 2023
0ac651b
Explicit logging
SimonBrandner Sep 6, 2023
6bc6d50
Update js-sdk
SimonBrandner Sep 6, 2023
ba5c042
Update js-sdk
SimonBrandner Sep 7, 2023
3a16dbe
Update encryption key on mute change
SimonBrandner Sep 7, 2023
3d57fac
Update js-sdk
SimonBrandner Sep 7, 2023
8bae276
Update js-sdk
SimonBrandner Sep 7, 2023
3a62ecc
Update js-sdk
SimonBrandner Sep 7, 2023
2f6b1ea
Update js-sdk
SimonBrandner Sep 7, 2023
ff99826
Update js-sdk
SimonBrandner Sep 7, 2023
a0f1184
Update js-sdk
SimonBrandner Sep 7, 2023
645b123
Handle indices
SimonBrandner Sep 10, 2023
fce220c
Update js-sdk
SimonBrandner Sep 10, 2023
d14d1e1
Merge remote-tracking branch 'upstream/livekit' into SimonBrandner/fe…
SimonBrandner Sep 12, 2023
28035b5
Post-merge fix
SimonBrandner Sep 12, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
"i18next-http-backend": "^1.4.4",
"livekit-client": "^1.12.3",
"lodash": "^4.17.21",
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#6836720e1e1c2cb01d49d6e5fcfc01afc14834ca",
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#0b72baada1dc9c14fb4ebe1b0595cef0acf4f2c8",
"matrix-widget-api": "^1.3.1",
"mermaid": "^9.0.0",
"normalize.css": "^8.0.1",
Expand Down
4 changes: 2 additions & 2 deletions src/E2EEBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ import { Trans } from "react-i18next";
import { Banner } from "./Banner";
import styles from "./E2EEBanner.module.css";
import { ReactComponent as LockOffIcon } from "./icons/LockOff.svg";
import { useEnableE2EE } from "./settings/useSetting";
import { useEnableE2EE } from "./e2ee/e2eeHooks";

export const E2EEBanner = () => {
const [e2eeEnabled] = useEnableE2EE();
const e2eeEnabled = useEnableE2EE();
if (e2eeEnabled) return null;

return (
Expand Down
5 changes: 5 additions & 0 deletions src/UrlParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ interface UrlParams {
* E2EE password
*/
password: string | null;
/**
* Whether we the app should use per participant keys for E2EE.
*/
perParticipantE2EE: boolean;
}

/**
Expand Down Expand Up @@ -190,6 +194,7 @@ export const getUrlParams = (
fontScale: Number.isNaN(fontScale) ? null : fontScale,
analyticsID: getParam("analyticsID"),
allowIceFallback: hasParam("allowIceFallback"),
perParticipantE2EE: hasParam("perParticipantE2EE"),
};
};

Expand Down
32 changes: 27 additions & 5 deletions src/e2ee/sharedKeyManagement.ts → src/e2ee/e2eeHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ limitations under the License.
*/

import { useEffect, useMemo } from "react";
import { isE2EESupported } from "livekit-client";

import { useEnableE2EE } from "../settings/useSetting";
import { useEnableSPAE2EE } from "../settings/useSetting";
import { useLocalStorage } from "../useLocalStorage";
import { useClient } from "../ClientContext";
import { PASSWORD_STRING, useUrlParams } from "../UrlParams";
Expand All @@ -28,7 +29,7 @@ export const useInternalRoomSharedKey = (
roomId: string
): [string | null, (value: string) => void] => {
const key = useMemo(() => getRoomSharedKeyLocalStorageKey(roomId), [roomId]);
const [e2eeEnabled] = useEnableE2EE();
const [e2eeEnabled] = useEnableSPAE2EE();
const [roomSharedKey, setRoomSharedKey] = useLocalStorage(key);

return [e2eeEnabled ? roomSharedKey : null, setRoomSharedKey];
Expand Down Expand Up @@ -67,19 +68,40 @@ export const useManageRoomSharedKey = (roomId: string): string | null => {
};

export const useIsRoomE2EE = (roomId: string): boolean | null => {
const { isEmbedded } = useUrlParams();
const { isEmbedded, perParticipantE2EE } = useUrlParams();
const client = useClient();
const room = useMemo(
() => client.client?.getRoom(roomId) ?? null,
[roomId, client.client]
);
const isE2EE = useMemo(() => {
if (isEmbedded) {
return false;
return perParticipantE2EE;
} else {
return room ? !room?.getCanonicalAlias() : null;
}
}, [isEmbedded, room]);
}, [room, isEmbedded, perParticipantE2EE]);

return isE2EE;
};

export const useEnableEmbeddedE2EE = (): boolean => {
const { isEmbedded, perParticipantE2EE } = useUrlParams();

if (!isEmbedded) return false;
if (!isE2EESupported()) return false;

return perParticipantE2EE;
};

export const useEnableE2EE = (): boolean => {
const [spaE2EEEnabled] = useEnableSPAE2EE();
const embeddedE2EEEnabled = useEnableEmbeddedE2EE();

const e2eeEnabled = useMemo(
() => spaE2EEEnabled || embeddedE2EEEnabled,
[spaE2EEEnabled, embeddedE2EEEnabled]
);

return e2eeEnabled;
};
69 changes: 69 additions & 0 deletions src/e2ee/matrixKeyProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
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 {
MatrixRTCSession,
MatrixRTCSessionEvent,
} from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";

export class MatrixKeyProvider extends BaseKeyProvider {
private rtcSession?: MatrixRTCSession;

public setRTCSession(rtcSession: MatrixRTCSession) {
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
) => {
this.onSetEncryptionKey(
await createKeyMaterialFromString(encryptionKey),
participantId,
encryptionKeyIndex
);

console.log(
`Embedded-E2EE-LOG onEncryptionKeyChanged participantId=${participantId} encryptionKeyIndex=${encryptionKeyIndex} encryptionKey=${encryptionKey}`,
this.getKeys()
);
};
}
2 changes: 1 addition & 1 deletion src/home/CallList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import styles from "./CallList.module.css";
import { getRoomUrl } from "../matrix-utils";
import { Body } from "../typography/Typography";
import { GroupCallRoom } from "./useGroupCallRooms";
import { useRoomSharedKey } from "../e2ee/sharedKeyManagement";
import { useRoomSharedKey } from "../e2ee/e2eeHooks";

interface CallListProps {
rooms: GroupCallRoom[];
Expand Down
6 changes: 3 additions & 3 deletions src/home/RegisteredView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,11 @@ import { JoinExistingCallModal } from "./JoinExistingCallModal";
import { Caption, Title } from "../typography/Typography";
import { Form } from "../form/Form";
import { CallType, CallTypeDropdown } from "./CallTypeDropdown";
import { useEnableE2EE, useOptInAnalytics } from "../settings/useSetting";
import { useEnableSPAE2EE, useOptInAnalytics } from "../settings/useSetting";
import { AnalyticsNotice } from "../analytics/AnalyticsNotice";
import { E2EEBanner } from "../E2EEBanner";
import { setLocalStorageItem } from "../useLocalStorage";
import { getRoomSharedKeyLocalStorageKey } from "../e2ee/sharedKeyManagement";
import { getRoomSharedKeyLocalStorageKey } from "../e2ee/e2eeHooks";

interface Props {
client: MatrixClient;
Expand All @@ -57,7 +57,7 @@ export function RegisteredView({ client, isPasswordlessUser }: Props) {
const history = useHistory();
const { t } = useTranslation();
const { modalState, modalProps } = useModalTriggerState();
const [e2eeEnabled] = useEnableE2EE();
const [e2eeEnabled] = useEnableSPAE2EE();

const onSubmit: FormEventHandler<HTMLFormElement> = useCallback(
(e: FormEvent) => {
Expand Down
6 changes: 3 additions & 3 deletions src/home/UnauthenticatedView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ import styles from "./UnauthenticatedView.module.css";
import commonStyles from "./common.module.css";
import { generateRandomName } from "../auth/generateRandomName";
import { AnalyticsNotice } from "../analytics/AnalyticsNotice";
import { useEnableE2EE, useOptInAnalytics } from "../settings/useSetting";
import { useEnableSPAE2EE, useOptInAnalytics } from "../settings/useSetting";
import { Config } from "../config/Config";
import { E2EEBanner } from "../E2EEBanner";
import { getRoomSharedKeyLocalStorageKey } from "../e2ee/sharedKeyManagement";
import { getRoomSharedKeyLocalStorageKey } from "../e2ee/e2eeHooks";
import { setLocalStorageItem } from "../useLocalStorage";

export const UnauthenticatedView: FC = () => {
Expand All @@ -60,7 +60,7 @@ export const UnauthenticatedView: FC = () => {
const history = useHistory();
const { t } = useTranslation();

const [e2eeEnabled] = useEnableE2EE();
const [e2eeEnabled] = useEnableSPAE2EE();

const onSubmit: FormEventHandler<HTMLFormElement> = useCallback(
(e) => {
Expand Down
46 changes: 33 additions & 13 deletions src/livekit/useLiveKit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ limitations under the License.

import {
ConnectionState,
E2EEOptions,
ExternalE2EEKeyProvider,
Room,
RoomOptions,
Expand All @@ -26,6 +25,7 @@ import { useLiveKitRoom } from "@livekit/components-react";
import { useEffect, useMemo, useRef } 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";
Expand All @@ -39,9 +39,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;
};

setLogLevel("debug");
Expand All @@ -53,25 +60,38 @@ interface UseLivekitResult {

export function useLiveKit(
muteStates: MuteStates,
rtcSession: MatrixRTCSession,
sfuConfig?: SFUConfig,
e2eeConfig?: E2EEConfig
): UseLivekitResult {
const e2eeOptions = useMemo(() => {
if (!e2eeConfig?.sharedKey) return 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;

(e2eeOptions.keyProvider as ExternalE2EEKeyProvider).setKey(
e2eeConfig?.sharedKey
);
}, [e2eeOptions, e2eeConfig?.sharedKey]);
if (!e2eeOptions) return;
if (!e2eeConfig) return;

if (e2eeConfig.mode === E2EEMode.PerParticipantKey) {
(e2eeOptions.keyProvider as MatrixKeyProvider).setRTCSession(rtcSession);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should setRTCSession be called with undefined somewhere to remove the listener?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume that if the class gets destroyed, so do the listeners, so there is no need to call it with undefined?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, not explicitly: there's no destructors run here (it's javascript) so the emitter will continue to retain a reference to the listener, we'd just be relying on them being GCed, so it depends whether anything else holds on to the thing doing the listening (the key provider in this case I think). Assuming everything gets torn down then everything should be detached from the GC root, but we shouldn't be relying on it given the choice IMO.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not really sure I see a case where this would be necessary -> I don't think I see where/when we should destroy it manually...

} 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();
Expand Down
Loading