From 041d41a5fee49a6e2a21cb2a675506582ae6bc2f Mon Sep 17 00:00:00 2001 From: lukasIO Date: Wed, 30 Aug 2023 11:25:22 +0200 Subject: [PATCH] Add e2ee option (#106) * Add checkbox for e2ee * add also to custom tab * fix SSR --- lib/DummyKeyProvider.ts | 16 ++++++++++++++++ lib/client-utils.ts | 11 +++++++++++ pages/custom/index.tsx | 36 +++++++++++++++++++++++++++++++++-- pages/index.tsx | 42 ++++++++++++++++++++++++++++++++++++----- pages/rooms/[name].tsx | 35 +++++++++++++++++++++++++++++++--- 5 files changed, 130 insertions(+), 10 deletions(-) create mode 100644 lib/DummyKeyProvider.ts diff --git a/lib/DummyKeyProvider.ts b/lib/DummyKeyProvider.ts new file mode 100644 index 0000000..b0c5b80 --- /dev/null +++ b/lib/DummyKeyProvider.ts @@ -0,0 +1,16 @@ +// just for demonstration purposes, extremely insecure + +import { BaseKeyProvider, createKeyMaterialFromString } from 'livekit-client'; + +export class DummyKeyProvider extends BaseKeyProvider { + readonly participantKeys = new Map([ + ['dev1', 'dev1key'], + ['dev2', 'dev2key'], + ]); + + async setKey(participantId: string) { + // @ts-ignore + const cryptoKey = await createKeyMaterialFromString(this.participantKeys.get(participantId)); + this.onSetEncryptionKey(cryptoKey, participantId); + } +} diff --git a/lib/client-utils.ts b/lib/client-utils.ts index a2401c8..a3471ea 100644 --- a/lib/client-utils.ts +++ b/lib/client-utils.ts @@ -19,3 +19,14 @@ export function useServerUrl(region?: string) { }); return serverUrl; } + +export function encodePassphrase(bytes: Uint8Array) { + const binString = Array.from(bytes, (x) => String.fromCodePoint(x)).join(''); + return btoa(binString); +} + +export function decodePassphrase(base64String: string) { + const binString = atob(base64String); + // @ts-ignore + return Uint8Array.from(binString, (m) => m.codePointAt(0)); +} diff --git a/pages/custom/index.tsx b/pages/custom/index.tsx index 3ae4ef1..aeed27a 100644 --- a/pages/custom/index.tsx +++ b/pages/custom/index.tsx @@ -1,11 +1,43 @@ import { formatChatMessageLinks, LiveKitRoom, VideoConference } from '@livekit/components-react'; -import { LogLevel } from 'livekit-client'; +import { ExternalE2EEKeyProvider, LogLevel, Room, RoomOptions, VideoPresets } from 'livekit-client'; import { useRouter } from 'next/router'; import { DebugMode } from '../../lib/Debug'; +import { decodePassphrase } from '../../lib/client-utils'; +import { useMemo } from 'react'; export default function CustomRoomConnection() { const router = useRouter(); const { liveKitUrl, token } = router.query; + + const hash = typeof window !== 'undefined' && window.location.hash; + const keyProvider = new ExternalE2EEKeyProvider(); + if (hash) { + keyProvider.setKey(decodePassphrase(hash.substring(1))); + } + + const worker = + typeof window !== 'undefined' && + new Worker(new URL('livekit-client/e2ee-worker', import.meta.url)); + + const roomOptions = useMemo((): RoomOptions => { + return { + publishDefaults: { + videoSimulcastLayers: [VideoPresets.h540, VideoPresets.h216], + }, + adaptiveStream: { pixelDensity: 'screen' }, + dynacast: true, + e2ee: + hash && worker + ? { + keyProvider, + worker, + } + : undefined, + }; + }, []); + + const room = useMemo(() => new Room(roomOptions), []); + if (typeof liveKitUrl !== 'string') { return

Missing LiveKit URL

; } @@ -16,7 +48,7 @@ export default function CustomRoomConnection() { return (
{liveKitUrl && ( - + diff --git a/pages/index.tsx b/pages/index.tsx index f988333..008af42 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -1,7 +1,8 @@ import type { GetServerSideProps, InferGetServerSidePropsType } from 'next'; import { useRouter } from 'next/router'; -import React, { ReactElement } from 'react'; +import React, { ReactElement, useState } from 'react'; import styles from '../styles/Home.module.css'; +import { encodePassphrase } from '../lib/client-utils'; interface TabsProps { children: ReactElement[]; @@ -38,13 +39,28 @@ function Tabs(props: TabsProps) { function DemoMeetingTab({ label }: { label: string }) { const router = useRouter(); + const [e2ee, setE2ee] = useState(false); const startMeeting = () => { - router.push(`/rooms/${generateRoomId()}`); + if (e2ee) { + const phrase = encodePassphrase(crypto.getRandomValues(new Uint8Array(256))); + router.push(`/rooms/${generateRoomId()}#${phrase}`); + } else { + router.push(`/rooms/${generateRoomId()}`); + } }; return (
-

Try LiveKit Meet for free with our live demo project.

-
@@ -53,12 +69,19 @@ function DemoMeetingTab({ label }: { label: string }) { function CustomConnectionTab({ label }: { label: string }) { const router = useRouter(); + const [e2ee, setE2ee] = useState(false); + const onSubmit: React.FormEventHandler = (event) => { event.preventDefault(); const formData = new FormData(event.target as HTMLFormElement); const serverUrl = formData.get('serverUrl'); const token = formData.get('token'); - router.push(`/custom/?liveKitUrl=${serverUrl}&token=${token}`); + if (e2ee) { + const passphrase = encodePassphrase(crypto.getRandomValues(new Uint8Array(256))); + router.push(`/custom/?liveKitUrl=${serverUrl}&token=${token}#${passphrase}`); + } else { + router.push(`/custom/?liveKitUrl=${serverUrl}&token=${token}`); + } }; return (
@@ -80,6 +103,15 @@ function CustomConnectionTab({ label }: { label: string }) { rows={9} style={{ padding: '1px 2px', fontSize: 'inherit', lineHeight: 'inherit' }} /> +
+ setE2ee(ev.target.checked)} + > + +

diff --git a/pages/rooms/[name].tsx b/pages/rooms/[name].tsx index da3828c..cadbd8f 100644 --- a/pages/rooms/[name].tsx +++ b/pages/rooms/[name].tsx @@ -6,14 +6,22 @@ import { VideoConference, formatChatMessageLinks, } from '@livekit/components-react'; -import { LogLevel, RoomConnectOptions, RoomOptions, VideoPresets } from 'livekit-client'; +import { + ExternalE2EEKeyProvider, + LogLevel, + Room, + RoomConnectOptions, + RoomOptions, + VideoPresets, +} from 'livekit-client'; import type { NextPage } from 'next'; import Head from 'next/head'; import { useRouter } from 'next/router'; import { useMemo, useState } from 'react'; import { DebugMode } from '../../lib/Debug'; -import { useServerUrl } from '../../lib/client-utils'; +import { decodePassphrase, useServerUrl } from '../../lib/client-utils'; +import { DummyKeyProvider } from '../../lib/DummyKeyProvider'; const Home: NextPage = () => { const router = useRouter(); @@ -78,6 +86,16 @@ const ActiveRoom = ({ roomName, userChoices, onLeave }: ActiveRoomProps) => { const liveKitUrl = useServerUrl(region as string | undefined); + const hash = typeof window !== 'undefined' && window.location.hash; + const keyProvider = new ExternalE2EEKeyProvider(); + if (hash) { + keyProvider.setKey(decodePassphrase(hash.substring(1))); + } + + const worker = + typeof window !== 'undefined' && + new Worker(new URL('livekit-client/e2ee-worker', import.meta.url)); + const roomOptions = useMemo((): RoomOptions => { return { videoCaptureDefaults: { @@ -95,9 +113,20 @@ const ActiveRoom = ({ roomName, userChoices, onLeave }: ActiveRoomProps) => { }, adaptiveStream: { pixelDensity: 'screen' }, dynacast: true, + e2ee: + hash && worker + ? { + keyProvider, + worker, + } + : undefined, }; }, [userChoices, hq]); + const room = useMemo(() => new Room(roomOptions), []); + + room.setE2EEEnabled(true); + const connectOptions = useMemo((): RoomConnectOptions => { return { autoSubscribe: false, @@ -108,9 +137,9 @@ const ActiveRoom = ({ roomName, userChoices, onLeave }: ActiveRoomProps) => { <> {liveKitUrl && (