Skip to content

Commit e0ef6dc

Browse files
cloudwebrtctheomonnom
authored andcommitted
feat: end-to-end encryption (#161)
1 parent a934d50 commit e0ef6dc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+2150
-250
lines changed

Cargo.lock

Lines changed: 164 additions & 178 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/Cargo.lock

Lines changed: 6 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ members = [
33
"basic_room",
44
"mobile",
55
"save_to_disk",
6-
"play_from_disk",
76
"wgpu_room",
87
"webhooks",
98
]

examples/save_to_disk/src/main.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ async fn main() {
9595
println!("Connected to room: {} - {}", room.name(), room.sid());
9696

9797
while let Some(msg) = rx.recv().await {
98+
#[allow(clippy::single_match)]
9899
match msg {
99100
RoomEvent::TrackSubscribed {
100101
track,
@@ -127,7 +128,7 @@ async fn record_track(audio_track: RemoteAudioTrack) -> Result<(), std::io::Erro
127128
let mut wav_writer = WavWriter::create(FILE_PATH, header).await?;
128129
let mut audio_stream = NativeAudioStream::new(rtc_track);
129130

130-
let max_record = 5 * header.sample_rate * header.num_channels as u32;
131+
let max_record = 5 * header.sample_rate * header.num_channels;
131132
let mut sample_count = 0;
132133
'recv_loop: while let Some(frame) = audio_stream.next().await {
133134
let data = resampler.remix_and_resample(

examples/wgpu_room/src/app.rs

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::{
44
video_renderer::VideoRenderer,
55
};
66
use egui::{Rounding, Stroke};
7-
use livekit::{prelude::*, SimulateScenario};
7+
use livekit::{e2ee::EncryptionType, prelude::*, SimulateScenario};
88
use std::collections::HashMap;
99

1010
/// The state of the application are saved on app exit and restored on app start.
@@ -13,7 +13,9 @@ use std::collections::HashMap;
1313
struct AppState {
1414
url: String,
1515
token: String,
16+
key: String,
1617
auto_subscribe: bool,
18+
enable_e2ee: bool,
1719
}
1820

1921
pub struct LkApp {
@@ -32,6 +34,8 @@ impl Default for AppState {
3234
url: "ws://localhost:7880".to_string(),
3335
token: "".to_string(),
3436
auto_subscribe: true,
37+
enable_e2ee: false,
38+
key: "".to_string(),
3539
}
3640
}
3741
}
@@ -122,6 +126,10 @@ impl LkApp {
122126
RoomEvent::Disconnected { reason: _ } => {
123127
self.video_renderers.clear();
124128
}
129+
RoomEvent::E2eeStateChanged { participant, state } => {
130+
let identity = participant.identity();
131+
log::info!("e2ee state changed {} - {:?}", identity, state)
132+
}
125133
_ => {}
126134
}
127135
}
@@ -179,6 +187,17 @@ impl LkApp {
179187
ui.text_edit_singleline(&mut self.state.token);
180188
});
181189

190+
ui.horizontal(|ui| {
191+
ui.label("E2EE Key: ");
192+
ui.text_edit_singleline(&mut self.state.key);
193+
});
194+
195+
ui.horizontal(|ui| {
196+
ui.add_enabled_ui(true, |ui| {
197+
ui.checkbox(&mut self.state.enable_e2ee, "Enable E2EE");
198+
});
199+
});
200+
182201
ui.horizontal(|ui| {
183202
ui.add_enabled_ui(!connected && !self.connecting, |ui| {
184203
if ui.button("Connect").clicked() {
@@ -188,19 +207,23 @@ impl LkApp {
188207
url: self.state.url.clone(),
189208
token: self.state.token.clone(),
190209
auto_subscribe: self.state.auto_subscribe,
210+
enable_e2ee: self.state.enable_e2ee,
211+
key: self.state.key.clone(),
191212
});
192213
}
193214
});
194215

195216
if self.connecting {
196217
ui.spinner();
197-
} else if connected {
198-
if ui.button("Disconnect").clicked() {
199-
let _ = self.service.send(AsyncCmd::RoomDisconnect);
200-
}
218+
} else if connected && ui.button("Disconnect").clicked() {
219+
let _ = self.service.send(AsyncCmd::RoomDisconnect);
201220
}
202221
});
203222

223+
if ui.button("E2eeKeyRatchet").clicked() {
224+
let _ = self.service.send(AsyncCmd::E2eeKeyRatchet);
225+
}
226+
204227
ui.horizontal(|ui| {
205228
ui.add_enabled_ui(true, |ui| {
206229
ui.checkbox(&mut self.state.auto_subscribe, "Auto Subscribe");
@@ -230,7 +253,9 @@ impl LkApp {
230253
ui.label("Participants");
231254
ui.separator();
232255

233-
let Some(room) = self.service.room() else { return; };
256+
let Some(room) = self.service.room() else {
257+
return;
258+
};
234259

235260
egui::ScrollArea::vertical().show(ui, |ui| {
236261
// Iterate with sorted keys to avoid flickers (Because this is a immediate mode UI)
@@ -239,18 +264,28 @@ impl LkApp {
239264
.keys()
240265
.cloned()
241266
.collect::<Vec<ParticipantSid>>();
242-
sorted_participants.sort_by(|a, b| a.as_str().cmp(&b.as_str()));
267+
sorted_participants.sort_by(|a, b| a.as_str().cmp(b.as_str()));
243268

244269
for psid in sorted_participants {
245270
let participant = participants.get(&psid).unwrap();
246271
let tracks = participant.tracks();
247272
let mut sorted_tracks = tracks.keys().cloned().collect::<Vec<TrackSid>>();
248-
sorted_tracks.sort_by(|a, b| a.as_str().cmp(&b.as_str()));
273+
sorted_tracks.sort_by(|a, b| a.as_str().cmp(b.as_str()));
249274

250275
ui.monospace(&participant.identity().0);
251276
for tsid in sorted_tracks {
252277
let publication = tracks.get(&tsid).unwrap().clone();
253278

279+
ui.horizontal(|ui| {
280+
ui.label("Encrypted - ");
281+
let enc_type = publication.encryption_type();
282+
if enc_type == EncryptionType::None {
283+
ui.colored_label(egui::Color32::RED, format!("{:?}", enc_type));
284+
} else {
285+
ui.colored_label(egui::Color32::GREEN, format!("{:?}", enc_type));
286+
}
287+
});
288+
254289
ui.label(format!(
255290
"{} - {:?}",
256291
publication.name(),
@@ -274,10 +309,8 @@ impl LkApp {
274309
.service
275310
.send(AsyncCmd::UnsubscribeTrack { publication });
276311
}
277-
} else {
278-
if ui.button("Subscribe").clicked() {
279-
let _ = self.service.send(AsyncCmd::SubscribeTrack { publication });
280-
}
312+
} else if ui.button("Subscribe").clicked() {
313+
let _ = self.service.send(AsyncCmd::SubscribeTrack { publication });
281314
}
282315
});
283316
}

examples/wgpu_room/src/service.rs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ use crate::{
22
logo_track::LogoTrack,
33
sine_track::{SineParameters, SineTrack},
44
};
5-
use livekit::{prelude::*, SimulateScenario};
5+
use livekit::{
6+
e2ee::{key_provider::*, E2eeOptions, EncryptionType},
7+
prelude::*,
8+
SimulateScenario,
9+
};
610
use parking_lot::Mutex;
711
use std::sync::Arc;
812
use tokio::sync::mpsc::{self, error::SendError};
@@ -13,6 +17,8 @@ pub enum AsyncCmd {
1317
url: String,
1418
token: String,
1519
auto_subscribe: bool,
20+
enable_e2ee: bool,
21+
key: String,
1622
},
1723
RoomDisconnect,
1824
SimulateScenario {
@@ -26,6 +32,7 @@ pub enum AsyncCmd {
2632
UnsubscribeTrack {
2733
publication: RemoteTrackPublication,
2834
},
35+
E2eeKeyRatchet,
2936
}
3037

3138
#[derive(Debug)]
@@ -102,14 +109,24 @@ async fn service_task(inner: Arc<ServiceInner>, mut cmd_rx: mpsc::UnboundedRecei
102109
url,
103110
token,
104111
auto_subscribe,
112+
enable_e2ee,
113+
key,
105114
} => {
106115
log::info!("connecting to room: {}", url);
107116

117+
let key_provider =
118+
KeyProvider::with_shared_key(KeyProviderOptions::default(), key.into_bytes());
119+
let e2ee = enable_e2ee.then_some(E2eeOptions {
120+
encryption_type: EncryptionType::Gcm,
121+
key_provider,
122+
});
123+
108124
let res = Room::connect(
109125
&url,
110126
&token,
111127
RoomOptions {
112128
auto_subscribe,
129+
e2ee,
113130
..Default::default()
114131
},
115132
)
@@ -174,6 +191,14 @@ async fn service_task(inner: Arc<ServiceInner>, mut cmd_rx: mpsc::UnboundedRecei
174191
AsyncCmd::UnsubscribeTrack { publication } => {
175192
publication.set_subscribed(false);
176193
}
194+
AsyncCmd::E2eeKeyRatchet => {
195+
if let Some(state) = running_state.as_ref() {
196+
let e2ee_manager = state.room.e2ee_manager();
197+
if let Some(key_provider) = e2ee_manager.key_provider() {
198+
key_provider.ratchet_shared_key(0);
199+
}
200+
}
201+
}
177202
}
178203
}
179204
}

examples/wgpu_room/src/video_renderer.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ impl VideoRenderer {
5050
let mut internal = internal.lock();
5151
let buffer = frame.buffer.to_i420();
5252

53-
let width: u32 = buffer.width().try_into().unwrap();
54-
let height: u32 = buffer.height().try_into().unwrap();
53+
let width: u32 = buffer.width();
54+
let height: u32 = buffer.height();
5555

5656
internal.ensure_texture_size(width, height);
5757

@@ -110,7 +110,7 @@ impl VideoRenderer {
110110

111111
// Returns the texture id, can be used to draw the texture on the UI
112112
pub fn texture_id(&self) -> Option<egui::TextureId> {
113-
self.internal.lock().egui_texture.clone()
113+
self.internal.lock().egui_texture
114114
}
115115
}
116116

@@ -160,14 +160,14 @@ impl RendererInternal {
160160
.renderer
161161
.write()
162162
.update_egui_texture_from_wgpu_texture(
163-
&*self.render_state.device,
163+
&self.render_state.device,
164164
self.texture_view.as_ref().unwrap(),
165165
wgpu::FilterMode::Linear,
166166
texture_id,
167167
);
168168
} else {
169169
self.egui_texture = Some(self.render_state.renderer.write().register_native_texture(
170-
&*self.render_state.device,
170+
&self.render_state.device,
171171
self.texture_view.as_ref().unwrap(),
172172
wgpu::FilterMode::Linear,
173173
));

livekit-ffi/generate_proto.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,5 @@ protoc \
2626
$PROTOCOL/track.proto \
2727
$PROTOCOL/participant.proto \
2828
$PROTOCOL/video_frame.proto \
29-
$PROTOCOL/audio_frame.proto
29+
$PROTOCOL/audio_frame.proto \
30+
$PROTOCOL/e2ee.proto

0 commit comments

Comments
 (0)