Refactor encryption information.

We had lots of enums and booleans to describe the encryption situation.
Now we only use the `EncryptionSystem` "enum" which contains the
additional information like sharedKey. (and we don't use the isRoomE2EE
function that is somewhat confusing since it checks `return widget ===
null && !room.getCanonicalAlias();` which is only indirectly related to
e2ee)

Signed-off-by: Timo K <toger5@hotmail.de>
This commit is contained in:
Timo K
2024-04-02 23:55:03 +02:00
committed by Andrew Ferrazzutti
parent 620c3f5d34
commit 69deece0a3
12 changed files with 87 additions and 93 deletions

View File

@@ -15,12 +15,11 @@ limitations under the License.
*/
import { useEffect, useMemo } from "react";
import { Room } from "matrix-js-sdk";
import { setLocalStorageItem, useLocalStorage } from "../useLocalStorage";
import { useClient } from "../ClientContext";
import { UrlParams, getUrlParams, useUrlParams } from "../UrlParams";
import { widget } from "../widget";
import { E2eeType } from "./e2eeType";
import { useClient } from "../ClientContext";
export function saveKeyForRoom(roomId: string, password: string): void {
setLocalStorageItem(getRoomSharedKeyLocalStorageKey(roomId), password);
@@ -68,30 +67,37 @@ const useKeyFromUrl = (): [string, string] | [undefined, undefined] => {
: [undefined, undefined];
};
export const useRoomSharedKey = (roomId: string): string | undefined => {
export type Unencrypted = { kind: E2eeType.NONE };
export type SharedSecret = { kind: E2eeType.SHARED_KEY; secret: string };
export type PerParticipantE2EE = { kind: E2eeType.PER_PARTICIPANT };
export type EncryptionSystem = Unencrypted | SharedSecret | PerParticipantE2EE;
export function useRoomEncryptionSystem(roomId: string): EncryptionSystem {
const { client } = useClient();
// make sure we've extracted the key from the URL first
// (and we still need to take the value it returns because
// the effect won't run in time for it to save to localstorage in
// time for us to read it out again).
const [urlRoomId, passwordFormUrl] = useKeyFromUrl();
const storedPassword = useInternalRoomSharedKey(roomId);
if (storedPassword) return storedPassword;
if (urlRoomId === roomId) return passwordFormUrl;
return undefined;
};
export const useIsRoomE2EE = (roomId: string): boolean | null => {
const { client } = useClient();
const room = useMemo(() => client?.getRoom(roomId), [roomId, client]);
return useMemo(() => !room || isRoomE2EE(room), [room]);
};
export function isRoomE2EE(room: Room): boolean {
// For now, rooms in widget mode are never considered encrypted.
// In the future, when widget mode gains encryption support, then perhaps we
// should inspect the e2eEnabled URL parameter here?
return widget === null && !room.getCanonicalAlias();
const room = client?.getRoom(roomId);
const e2eeSystem = useMemo(() => {
if (!room) return { kind: E2eeType.NONE } as Unencrypted;
if (storedPassword)
return {
kind: E2eeType.SHARED_KEY,
secret: storedPassword,
} as SharedSecret;
if (urlRoomId === roomId)
return {
kind: E2eeType.SHARED_KEY,
secret: passwordFormUrl,
} as SharedSecret;
if (room.hasEncryptionStateEvent()) {
return { kind: E2eeType.PER_PARTICIPANT } as PerParticipantE2EE;
}
return { kind: E2eeType.NONE } as EncryptionSystem;
}, [passwordFormUrl, room, roomId, storedPassword, urlRoomId]);
return e2eeSystem;
}

View File

@@ -78,12 +78,14 @@ export const RegisteredView: FC<Props> = ({ client }) => {
roomName,
E2eeType.SHARED_KEY,
);
if (!createRoomResult.password)
throw new Error("Failed to create room with shared secret");
history.push(
getRelativeRoomUrl(
createRoomResult.roomId,
{ kind: E2eeType.SHARED_KEY, secret: createRoomResult.password },
roomName,
createRoomResult.password,
),
);
}

View File

@@ -116,13 +116,15 @@ export const UnauthenticatedView: FC = () => {
if (!setClient) {
throw new Error("setClient is undefined");
}
if (!createRoomResult.password)
throw new Error("Failed to create room with shared secret");
setClient({ client, session });
history.push(
getRelativeRoomUrl(
createRoomResult.roomId,
{ kind: E2eeType.SHARED_KEY, secret: createRoomResult.password },
roomName,
createRoomResult.password,
),
);
}

View File

@@ -41,11 +41,7 @@ import {
} from "./useECConnectionState";
import { MatrixKeyProvider } from "../e2ee/matrixKeyProvider";
import { E2eeType } from "../e2ee/e2eeType";
export type E2EEConfig = {
mode: E2eeType;
sharedKey?: string;
};
import { EncryptionSystem } from "../e2ee/sharedKeyManagement";
interface UseLivekitResult {
livekitRoom?: Room;
@@ -56,41 +52,35 @@ export function useLiveKit(
rtcSession: MatrixRTCSession,
muteStates: MuteStates,
sfuConfig: SFUConfig | undefined,
e2eeConfig: E2EEConfig,
e2eeSystem: EncryptionSystem,
): UseLivekitResult {
const e2eeOptions = useMemo((): E2EEOptions | undefined => {
if (e2eeConfig.mode === E2eeType.NONE) return undefined;
if (e2eeSystem.kind === E2eeType.NONE) return undefined;
if (e2eeConfig.mode === E2eeType.PER_PARTICIPANT) {
if (e2eeSystem.kind === E2eeType.PER_PARTICIPANT) {
return {
keyProvider: new MatrixKeyProvider(),
worker: new E2EEWorker(),
};
} else if (
e2eeConfig.mode === E2eeType.SHARED_KEY &&
e2eeConfig.sharedKey
) {
} else if (e2eeSystem.kind === E2eeType.SHARED_KEY && e2eeSystem.secret) {
return {
keyProvider: new ExternalE2EEKeyProvider(),
worker: new E2EEWorker(),
};
}
}, [e2eeConfig]);
}, [e2eeSystem]);
useEffect(() => {
if (e2eeConfig.mode === E2eeType.NONE || !e2eeOptions) return;
if (e2eeSystem.kind === E2eeType.NONE || !e2eeOptions) return;
if (e2eeConfig.mode === E2eeType.PER_PARTICIPANT) {
if (e2eeSystem.kind === E2eeType.PER_PARTICIPANT) {
(e2eeOptions.keyProvider as MatrixKeyProvider).setRTCSession(rtcSession);
} else if (
e2eeConfig.mode === E2eeType.SHARED_KEY &&
e2eeConfig.sharedKey
) {
} else if (e2eeSystem.kind === E2eeType.SHARED_KEY && e2eeSystem.secret) {
(e2eeOptions.keyProvider as ExternalE2EEKeyProvider).setKey(
e2eeConfig.sharedKey,
e2eeSystem.secret,
);
}
}, [e2eeOptions, e2eeConfig, rtcSession]);
}, [e2eeOptions, e2eeSystem, rtcSession]);
const initialMuteStates = useRef<MuteStates>(muteStates);
const devices = useMediaDevices();
@@ -131,9 +121,9 @@ export function useLiveKit(
// useEffect() with an argument that references itself, if E2EE is enabled
const room = useMemo(() => {
const r = new Room(roomOptions);
r.setE2EEEnabled(e2eeConfig.mode !== E2eeType.NONE);
r.setE2EEEnabled(e2eeSystem.kind !== E2eeType.NONE);
return r;
}, [roomOptions, e2eeConfig]);
}, [roomOptions, e2eeSystem]);
const connectionState = useECConnectionState(
{

View File

@@ -21,13 +21,14 @@ import PopOutIcon from "@vector-im/compound-design-tokens/icons/pop-out.svg?reac
import { logger } from "matrix-js-sdk/src/logger";
import { Modal } from "../Modal";
import { useIsRoomE2EE, useRoomSharedKey } from "../e2ee/sharedKeyManagement";
import { useRoomEncryptionSystem } from "../e2ee/sharedKeyManagement";
import { getAbsoluteRoomUrl } from "../matrix-utils";
import styles from "./AppSelectionModal.module.css";
import { editFragmentQuery } from "../UrlParams";
import { E2eeType } from "../e2ee/e2eeType";
interface Props {
roomId: string | null;
roomId: string;
}
export const AppSelectionModal: FC<Props> = ({ roomId }) => {
@@ -42,10 +43,9 @@ export const AppSelectionModal: FC<Props> = ({ roomId }) => {
},
[setOpen],
);
const e2eeSystem = useRoomEncryptionSystem(roomId);
const roomSharedKey = useRoomSharedKey(roomId ?? "");
const roomIsEncrypted = useIsRoomE2EE(roomId ?? "");
if (roomIsEncrypted && roomSharedKey === undefined) {
if (e2eeSystem.kind === E2eeType.NONE) {
logger.error(
"Generating app redirect URL for encrypted room but don't have key available!",
);
@@ -60,7 +60,7 @@ export const AppSelectionModal: FC<Props> = ({ roomId }) => {
const url = new URL(
roomId === null
? window.location.href
: getAbsoluteRoomUrl(roomId, undefined, roomSharedKey ?? undefined),
: getAbsoluteRoomUrl(roomId, e2eeSystem),
);
// Edit the URL to prevent the app selection prompt from appearing a second
// time within the app, and to keep the user confined to the current room
@@ -73,7 +73,7 @@ export const AppSelectionModal: FC<Props> = ({ roomId }) => {
const result = new URL("io.element.call:/");
result.searchParams.set("url", url.toString());
return result.toString();
}, [roomId, roomSharedKey]);
}, [e2eeSystem, roomId]);
return (
<Modal

View File

@@ -17,7 +17,10 @@ limitations under the License.
import { FC, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useHistory } from "react-router-dom";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { Room, isE2EESupported } from "livekit-client";
import {
Room,
isE2EESupported as isE2EESupportedBrowser,
} from "livekit-client";
import { logger } from "matrix-js-sdk/src/logger";
import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
import { JoinRule } from "matrix-js-sdk/src/matrix";
@@ -39,12 +42,11 @@ import { useMediaDevices, MediaDevices } from "../livekit/MediaDevicesContext";
import { useMatrixRTCSessionMemberships } from "../useMatrixRTCSessionMemberships";
import { enterRTCSession, leaveRTCSession } from "../rtcSessionHelpers";
import { useMatrixRTCSessionJoinState } from "../useMatrixRTCSessionJoinState";
import { useIsRoomE2EE, useRoomSharedKey } from "../e2ee/sharedKeyManagement";
import { useRoomEncryptionSystem } from "../e2ee/sharedKeyManagement";
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";
@@ -86,10 +88,8 @@ export const GroupCallView: FC<Props> = ({
const { displayName, avatarUrl } = useProfile(client);
const roomName = useRoomName(rtcSession.room);
const roomAvatar = useRoomAvatar(rtcSession.room);
const e2eeSharedKey = useRoomSharedKey(rtcSession.room.roomId);
const { perParticipantE2EE, returnToLobby } = useUrlParams();
const roomEncrypted =
useIsRoomE2EE(rtcSession.room.roomId) || perParticipantE2EE;
const e2eeSystem = useRoomEncryptionSystem(rtcSession.room.roomId);
const matrixInfo = useMemo((): MatrixInfo => {
return {
@@ -100,16 +100,16 @@ export const GroupCallView: FC<Props> = ({
roomName,
roomAlias: rtcSession.room.getCanonicalAlias(),
roomAvatar,
roomEncrypted,
e2eeSystem,
};
}, [
client,
displayName,
avatarUrl,
rtcSession,
rtcSession.room,
roomName,
roomAvatar,
roomEncrypted,
client,
e2eeSystem,
]);
// Count each member only once, regardless of how many devices they use
@@ -126,16 +126,6 @@ export const GroupCallView: FC<Props> = ({
const latestMuteStates = useRef<MuteStates>();
latestMuteStates.current = muteStates;
const e2eeConfig = useMemo((): E2EEConfig => {
if (perParticipantE2EE) {
return { mode: E2eeType.PER_PARTICIPANT };
} else if (e2eeSharedKey) {
return { mode: E2eeType.SHARED_KEY, sharedKey: e2eeSharedKey };
} else {
return { mode: E2eeType.NONE };
}
}, [perParticipantE2EE, e2eeSharedKey]);
useEffect(() => {
const defaultDeviceSetup = async (
requestedDeviceData: JoinCallData,
@@ -288,17 +278,18 @@ export const GroupCallView: FC<Props> = ({
const { t } = useTranslation();
if (roomEncrypted && !perParticipantE2EE && !e2eeSharedKey) {
if (e2eeSystem.kind === E2eeType.NONE) {
return (
<ErrorView
error={
new Error(
"No E2EE key provided: please make sure the URL you're using to join this call has been retrieved using the in-app button.",
"No E2EE key or other encryption system provided: Element call calls are always encrypted, please make sure the URL you're using to join this call has been retrieved using the in-app button.",
)
}
/>
);
} else if (!isE2EESupported() && roomEncrypted) {
} else if (!isE2EESupportedBrowser()) {
// and we have a encryption system.
return (
<FullScreenView>
<Heading>{t("browser_media_e2ee_unsupported_heading")}</Heading>
@@ -345,7 +336,7 @@ export const GroupCallView: FC<Props> = ({
onLeave={onLeave}
hideHeader={hideHeader}
muteStates={muteStates}
e2eeConfig={e2eeConfig}
e2eeSystem={e2eeSystem}
//otelGroupCallMembership={otelGroupCallMembership}
onShareClick={onShareClick}
/>

View File

@@ -63,7 +63,7 @@ import { OTelGroupCallMembership } from "../otel/OTelGroupCallMembership";
import { SettingsModal, defaultSettingsTab } from "../settings/SettingsModal";
import { useRageshakeRequestModal } from "../settings/submit-rageshake";
import { RageshakeRequestModal } from "./RageshakeRequestModal";
import { E2EEConfig, useLiveKit } from "../livekit/useLiveKit";
import { useLiveKit } from "../livekit/useLiveKit";
import { useFullscreen } from "./useFullscreen";
import { useLayoutStates } from "../video-grid/Layout";
import { useWakeLock } from "../useWakeLock";
@@ -76,13 +76,15 @@ import { ECConnectionState } from "../livekit/useECConnectionState";
import { useOpenIDSFU } from "../livekit/openIDSFU";
import { useCallViewModel } from "../state/CallViewModel";
import { subscribe } from "../state/subscribe";
import { EncryptionSystem } from "../e2ee/sharedKeyManagement";
import { E2eeType } from "../e2ee/e2eeType";
const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {});
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
export interface ActiveCallProps
extends Omit<InCallViewProps, "livekitRoom" | "connState"> {
e2eeConfig: E2EEConfig;
e2eeSystem: EncryptionSystem;
}
export const ActiveCall: FC<ActiveCallProps> = (props) => {
@@ -91,7 +93,7 @@ export const ActiveCall: FC<ActiveCallProps> = (props) => {
props.rtcSession,
props.muteStates,
sfuConfig,
props.e2eeConfig,
props.e2eeSystem,
);
useEffect(() => {
@@ -238,7 +240,7 @@ export const InCallView: FC<InCallViewProps> = subscribe(
const vm = useCallViewModel(
rtcSession.room,
livekitRoom,
matrixInfo.roomEncrypted,
matrixInfo.e2eeSystem.kind !== E2eeType.NONE,
connState,
);
const items = useStateObservable(vm.tiles);
@@ -432,7 +434,7 @@ export const InCallView: FC<InCallViewProps> = subscribe(
id={matrixInfo.roomId}
name={matrixInfo.roomName}
avatarUrl={matrixInfo.roomAvatar}
encrypted={matrixInfo.roomEncrypted}
encrypted={matrixInfo.e2eeSystem.kind !== E2eeType.NONE}
participantCount={participantCount}
/>
</LeftNav>

View File

@@ -25,8 +25,8 @@ import useClipboard from "react-use-clipboard";
import { Modal } from "../Modal";
import { getAbsoluteRoomUrl } from "../matrix-utils";
import styles from "./InviteModal.module.css";
import { useRoomSharedKey } from "../e2ee/sharedKeyManagement";
import { Toast } from "../Toast";
import { useRoomEncryptionSystem } from "../e2ee/sharedKeyManagement";
interface Props {
room: Room;
@@ -36,11 +36,11 @@ interface Props {
export const InviteModal: FC<Props> = ({ room, open, onDismiss }) => {
const { t } = useTranslation();
const roomSharedKey = useRoomSharedKey(room.roomId);
const e2eeSystem = useRoomEncryptionSystem(room.roomId);
const url = useMemo(
() =>
getAbsoluteRoomUrl(room.roomId, room.name, roomSharedKey ?? undefined),
[room, roomSharedKey],
() => getAbsoluteRoomUrl(room.roomId, e2eeSystem, room.name),
[e2eeSystem, room.name, room.roomId],
);
const [, setCopied] = useClipboard(url);
const [toastOpen, setToastOpen] = useState(false);

View File

@@ -36,6 +36,7 @@ import {
} from "../button/Button";
import { SettingsModal, defaultSettingsTab } from "../settings/SettingsModal";
import { useMediaQuery } from "../useMediaQuery";
import { E2eeType } from "../e2ee/e2eeType";
interface Props {
client: MatrixClient;
@@ -104,7 +105,7 @@ export const LobbyView: FC<Props> = ({
id={matrixInfo.roomId}
name={matrixInfo.roomName}
avatarUrl={matrixInfo.roomAvatar}
encrypted={matrixInfo.roomEncrypted}
encrypted={matrixInfo.e2eeSystem.kind !== E2eeType.NONE}
participantCount={participantCount}
/>
</LeftNav>

View File

@@ -32,6 +32,7 @@ import styles from "./VideoPreview.module.css";
import { useMediaDevices } from "../livekit/MediaDevicesContext";
import { MuteStates } from "./MuteStates";
import { useMediaQuery } from "../useMediaQuery";
import { EncryptionSystem } from "../e2ee/sharedKeyManagement";
export type MatrixInfo = {
userId: string;
@@ -41,7 +42,7 @@ export type MatrixInfo = {
roomName: string;
roomAlias: string | null;
roomAvatar: string | null;
roomEncrypted: boolean;
e2eeSystem: EncryptionSystem;
};
interface Props {

View File

@@ -53,8 +53,6 @@ export const WaitForInviteView: FC<Props> = ({
[setSettingsModalOpen],
);
// TODO: Unify this component with InCallView, so we can get slick joining
// animations and don't have to feel bad about reusing its CSS
return (
<>
<div className={classNames(styles.room, inCallStyles.inRoom)}>

View File

@@ -122,6 +122,7 @@ export const widget = ((): WidgetHelpers | null => {
];
const receiveState = [
{ eventType: EventType.RoomMember },
{ eventType: EventType.RoomEncryption },
{ eventType: EventType.GroupCallPrefix },
{ eventType: EventType.GroupCallMemberPrefix },
];