|
24 | 24 |
|
25 | 25 | namespace livekit { |
26 | 26 |
|
27 | | -/* Encryption algorithm type used by the underlying stack. Keep this aligned |
28 | | - * with your proto enum. */ |
29 | | -enum class EncryptionType { NONE = 0, GCM = 1, CUSTOM = 2 }; |
30 | | - |
31 | | -/// End-to-end encryption (E2EE) configuration. |
32 | | -/// |
33 | | -/// When enabled, media frames are encrypted before being sent and |
34 | | -/// decrypted on receipt. Keys may be provided up-front (shared-key mode) |
35 | | -/// or supplied by other mechanisms supported by the underlying runtime. |
36 | | -struct E2EEOptions { |
37 | | - bool enabled; |
38 | | - /// Encryption algorithm to use. |
39 | | - /// |
40 | | - /// GCM is the default and recommended option. |
41 | | - EncryptionType encryption_type = EncryptionType::GCM; |
| 27 | +/* Encryption algorithm type used by the underlying stack. |
| 28 | + * Keep this aligned with your proto enum values. */ |
| 29 | +enum class EncryptionType { |
| 30 | + NONE = 0, |
| 31 | + GCM = 1, |
| 32 | + CUSTOM = 2, |
| 33 | +}; |
42 | 34 |
|
43 | | - /// Shared static key for shared-key E2EE. |
| 35 | +/* Defaults (match other SDKs / Python defaults). */ |
| 36 | +inline constexpr const char *kDefaultRatchetSalt = "LKFrameEncryptionKey"; |
| 37 | +inline constexpr int kDefaultRatchetWindowSize = 16; |
| 38 | +inline constexpr int kDefaultFailureTolerance = -1; |
| 39 | + |
| 40 | +/** |
| 41 | + * Options for configuring the key provider used by E2EE. |
| 42 | + * |
| 43 | + * Notes: |
| 44 | + * - `shared_key` is optional. If omitted, the application may set keys later |
| 45 | + * (e.g. via KeyProvider::setSharedKey / per-participant keys). |
| 46 | + * - `ratchet_salt` may be empty to indicate "use implementation default". |
| 47 | + * - `ratchet_window_size` and `failure_tolerance` use SDK defaults unless |
| 48 | + * overridden. |
| 49 | + */ |
| 50 | +struct EncryptionKeyProviderOptions { |
| 51 | + /// Shared static key for "shared-key E2EE" (optional). |
44 | 52 | /// |
45 | | - /// When using shared-key E2EE, this key must be provided and must be |
46 | | - /// identical (byte-for-byte) for all participants in the room in order |
47 | | - /// to successfully encrypt and decrypt media. |
| 53 | + /// If set, it must be identical (byte-for-byte) across all participants |
| 54 | + /// that are expected to decrypt each other’s media. |
48 | 55 | /// |
49 | | - /// If this is empty while E2EE is enabled, media cannot be decrypted |
50 | | - /// and participants will not be able to communicate (e.g. black video / |
51 | | - /// silent audio). |
52 | | - std::vector<std::uint8_t> shared_key; |
| 56 | + /// If not set, keys must be provided out-of-band later (e.g. via KeyProvider |
| 57 | + /// APIs). |
| 58 | + std::optional<std::vector<std::uint8_t>> shared_key; |
53 | 59 |
|
54 | | - /// Optional salt used when deriving ratcheted encryption keys. |
| 60 | + /// Salt used when deriving ratcheted keys. |
55 | 61 | /// |
56 | | - /// If empty, a default salt is used by the underlying implementation. |
57 | | - std::vector<std::uint8_t> ratchet_salt; |
| 62 | + /// If empty, the underlying implementation default is used. |
| 63 | + std::vector<std::uint8_t> ratchet_salt = std::vector<std::uint8_t>( |
| 64 | + kDefaultRatchetSalt, kDefaultRatchetSalt + std::char_traits<char>::length( |
| 65 | + kDefaultRatchetSalt)); |
58 | 66 |
|
59 | | - /// Optional ratchet window size. |
60 | | - /// |
61 | 67 | /// Controls how many previous keys are retained during ratcheting. |
62 | | - /// A value of 0 indicates that the implementation default is used. |
63 | | - int ratchet_window_size = 0; |
| 68 | + int ratchet_window_size = kDefaultRatchetWindowSize; |
64 | 69 |
|
65 | | - /// Optional failure tolerance for ratcheting. |
66 | | - /// |
67 | | - /// Specifies how many consecutive ratcheting failures are tolerated |
68 | | - /// before encryption errors are reported. A value of 0 indicates |
69 | | - /// that the implementation default is used. |
70 | | - int failure_tolerance = 0; |
| 70 | + /// Number of tolerated ratchet failures before reporting encryption errors. |
| 71 | + int failure_tolerance = kDefaultFailureTolerance; |
| 72 | +}; |
| 73 | + |
| 74 | +/** |
| 75 | + * End-to-end encryption (E2EE) configuration for a room. |
| 76 | + * |
| 77 | + * Provide this in RoomOptions to initialize E2EE support. |
| 78 | + * |
| 79 | + * IMPORTANT: |
| 80 | + * - Providing E2EEOptions means "E2EE support is configured for this room". |
| 81 | + * - Whether encryption is actively applied can still be toggled at runtime via |
| 82 | + * E2EEManager::setEnabled(). |
| 83 | + * - A room can be configured for E2EE even if no shared key is provided yet. |
| 84 | + * In that case, the app must supply keys later via KeyProvider (shared-key or |
| 85 | + * per-participant). |
| 86 | + */ |
| 87 | +struct E2EEOptions { |
| 88 | + EncryptionKeyProviderOptions key_provider_options{}; |
| 89 | + EncryptionType encryption_type = EncryptionType::GCM; // default & recommended |
71 | 90 | }; |
72 | 91 |
|
| 92 | +/** |
| 93 | + * E2EE manager for a connected room. |
| 94 | + * |
| 95 | + * Lifetime: |
| 96 | + * - Owned by Room. Applications must not construct E2EEManager directly. |
| 97 | + * |
| 98 | + * Enablement model: |
| 99 | + * - If the Room was created with `RoomOptions.e2ee` set, the room will expose |
| 100 | + * a non-null E2EEManager via Room::E2eeManager(). |
| 101 | + * - If the Room was created without E2EE options, Room::E2eeManager() may be |
| 102 | + * null. |
| 103 | + * |
| 104 | + * Key model: |
| 105 | + * - Keys are managed via KeyProvider (shared-key or per-participant). |
| 106 | + * - Providing a shared key up-front is convenient for shared-key E2EE, but is |
| 107 | + * not required by the API shape (keys may be supplied later). |
| 108 | + */ |
73 | 109 | class E2EEManager { |
74 | 110 | public: |
75 | | - virtual ~E2EEManager(); |
76 | | - |
| 111 | + /** If your application requires key rotation during the lifetime of a single |
| 112 | + * room or unique keys per participant (such as when implementing the MEGOLM |
| 113 | + * or MLS protocol), you' can do it via key provider and frame cryptor. refer |
| 114 | + * https://docs.livekit.io/home/client/encryption/#custom-key-provider doe |
| 115 | + * details |
| 116 | + * */ |
| 117 | + class KeyProvider { |
| 118 | + public: |
| 119 | + ~KeyProvider() = default; |
| 120 | + |
| 121 | + KeyProvider(const KeyProvider &) = delete; |
| 122 | + KeyProvider &operator=(const KeyProvider &) = delete; |
| 123 | + KeyProvider(KeyProvider &&) noexcept = default; |
| 124 | + KeyProvider &operator=(KeyProvider &&) noexcept = default; |
| 125 | + |
| 126 | + /// Returns the options used to initialize this KeyProvider. |
| 127 | + const EncryptionKeyProviderOptions &options() const; |
| 128 | + |
| 129 | + /// Sets the shared key for the given key slot. |
| 130 | + void setSharedKey(const std::vector<std::uint8_t> &key, int key_index = 0); |
| 131 | + |
| 132 | + /// Exports the shared key for a given key slot. |
| 133 | + std::vector<std::uint8_t> exportSharedKey(int key_index = 0) const; |
| 134 | + |
| 135 | + /// Ratchets the shared key at key_index and returns the newly derived key. |
| 136 | + std::vector<std::uint8_t> ratchetSharedKey(int key_index = 0); |
| 137 | + |
| 138 | + /// Sets a key for a specific participant identity. |
| 139 | + void setKey(const std::string &participant_identity, |
| 140 | + const std::vector<std::uint8_t> &key, int key_index = 0); |
| 141 | + |
| 142 | + /// Exports a participant-specific key. |
| 143 | + std::vector<std::uint8_t> exportKey(const std::string &participant_identity, |
| 144 | + int key_index = 0) const; |
| 145 | + |
| 146 | + /// Ratchets a participant-specific key and returns the new key. |
| 147 | + std::vector<std::uint8_t> |
| 148 | + ratchetKey(const std::string &participant_identity, int key_index = 0); |
| 149 | + |
| 150 | + private: |
| 151 | + friend class E2EEManager; |
| 152 | + KeyProvider(std::uint64_t room_handle, |
| 153 | + EncryptionKeyProviderOptions options); |
| 154 | + std::uint64_t room_handle_{0}; |
| 155 | + EncryptionKeyProviderOptions options_; |
| 156 | + }; |
| 157 | + |
| 158 | + class FrameCryptor { |
| 159 | + public: |
| 160 | + FrameCryptor(std::uint64_t room_handle, std::string participant_identity, |
| 161 | + int key_index, bool enabled); |
| 162 | + ~FrameCryptor() = default; |
| 163 | + FrameCryptor(const FrameCryptor &) = delete; |
| 164 | + FrameCryptor &operator=(const FrameCryptor &) = delete; |
| 165 | + FrameCryptor(FrameCryptor &&) noexcept = default; |
| 166 | + FrameCryptor &operator=(FrameCryptor &&) noexcept = default; |
| 167 | + |
| 168 | + const std::string &participantIdentity() const; |
| 169 | + int keyIndex() const; |
| 170 | + bool enabled() const; |
| 171 | + |
| 172 | + /// Enables or disables frame encryption/decryption for this participant. |
| 173 | + void setEnabled(bool enabled); |
| 174 | + |
| 175 | + /// Sets the active key index for this participant cryptor. |
| 176 | + void setKeyIndex(int key_index); |
| 177 | + |
| 178 | + private: |
| 179 | + std::uint64_t room_handle_{0}; |
| 180 | + bool enabled_{false}; |
| 181 | + std::string participant_identity_; |
| 182 | + int key_index_{0}; |
| 183 | + }; |
| 184 | + |
| 185 | + ~E2EEManager() = default; |
77 | 186 | E2EEManager(const E2EEManager &) = delete; |
78 | 187 | E2EEManager &operator=(const E2EEManager &) = delete; |
| 188 | + E2EEManager(E2EEManager &&) noexcept = delete; |
| 189 | + E2EEManager &operator=(E2EEManager &&) noexcept = delete; |
79 | 190 |
|
80 | | - E2EEManager(E2EEManager &&) noexcept; |
81 | | - E2EEManager &operator=(E2EEManager &&) noexcept; |
82 | | - |
83 | | - /** |
84 | | - * Returns whether end-to-end encryption (E2EE) is currently enabled. |
85 | | - * |
86 | | - * This reflects the runtime encryption state for media tracks |
87 | | - * associated with the room. |
88 | | - */ |
| 191 | + /// Returns whether E2EE is currently enabled for this room at runtime. |
89 | 192 | bool enabled() const; |
90 | 193 |
|
91 | | - /** |
92 | | - * Enable or disable end-to-end encryption at runtime. |
93 | | - * |
94 | | - * Disabling E2EE will stop encrypting outgoing media and stop |
95 | | - * decrypting incoming media. |
96 | | - * |
97 | | - * NOTE: |
98 | | - * - All participants must agree on E2EE state and keys in order |
99 | | - * to successfully exchange media. |
100 | | - * - Disabling E2EE while other participants still have it enabled |
101 | | - * will result in media being undecodable. |
102 | | - */ |
| 194 | + /// Enable or disable E2EE at runtime. |
| 195 | + /// |
| 196 | + /// NOTE: |
| 197 | + /// - Enabling E2EE without having compatible keys set across participants |
| 198 | + /// will result in undecodable media (black video / silent audio). |
103 | 199 | void setEnabled(bool enabled); |
104 | 200 |
|
105 | | - /** |
106 | | - * Set or replace the shared encryption key at the given key index. |
107 | | - * |
108 | | - * This is typically used for: |
109 | | - * - Manual key rotation |
110 | | - * |
111 | | - * The provided key MUST be identical across all participants |
112 | | - * using shared-key E2EE, otherwise media decryption will fail. |
113 | | - * |
114 | | - * @param key Raw key bytes |
115 | | - * @param key_index Index of the key to set (default: 0) |
116 | | - */ |
117 | | - void setSharedKey(const std::vector<std::uint8_t> &key, int key_index = 0); |
118 | | - |
119 | | - /** |
120 | | - * Export the currently active shared key at the given key index. |
121 | | - * |
122 | | - * This API is primarily intended for debugging, verification, |
123 | | - * or diagnostics. Applications should avoid exporting keys |
124 | | - * unless absolutely necessary. |
125 | | - * |
126 | | - * @param key_index Index of the key to export (default: 0) |
127 | | - * @return Raw key bytes |
128 | | - */ |
129 | | - std::vector<std::uint8_t> exportSharedKey(int key_index = 0) const; |
130 | | - |
131 | | - /** |
132 | | - * Ratchet (derive) a new shared key at the given key index. |
133 | | - * |
134 | | - * This advances the key forward and returns the newly derived key. |
135 | | - * All participants must ratchet keys in the same order to remain |
136 | | - * in sync. |
137 | | - * |
138 | | - * @param key_index Index of the key to ratchet (default: 0) |
139 | | - * @return Newly derived key bytes |
140 | | - */ |
141 | | - std::vector<std::uint8_t> ratchetSharedKey(int key_index = 0); |
| 201 | + /// Returns the key provider if E2EE was configured for the room; otherwise |
| 202 | + /// nullptr. |
| 203 | + KeyProvider *keyProvider(); |
| 204 | + const KeyProvider *keyProvider() const; |
| 205 | + |
| 206 | + /// Retrieves the current list of frame cryptors from the underlying runtime. |
| 207 | + std::vector<E2EEManager::FrameCryptor> frameCryptors() const; |
142 | 208 |
|
143 | 209 | protected: |
144 | | - /* |
145 | | - * Construct an E2EE manager for a connected room. |
146 | | - * |
147 | | - * This constructor are intended for internal use by room. |
148 | | - * Applications should NOT create their own E2EEManager instances. |
149 | | - * |
150 | | - * After successfully connecting to a room with E2EE enabled, |
151 | | - * obtain the E2EE manager via the Room: |
152 | | - * |
153 | | - * auto e2ee_manager = room->e2eeManager(); |
154 | | - * |
155 | | - * The Room owns and manages the lifetime of the E2EEManager and ensures |
156 | | - * it is correctly wired to the underlying room handle and track lifecycle. |
157 | | - */ |
158 | | - explicit E2EEManager(std::uint64_t room_handle, E2EEOptions config); |
| 210 | + /// Internal constructor used by Room when E2EEOptions are provided. |
| 211 | + explicit E2EEManager(std::uint64_t room_handle, const E2EEOptions &options); |
159 | 212 | friend class Room; |
160 | 213 |
|
161 | 214 | private: |
162 | | - struct Impl; |
163 | | - std::unique_ptr<Impl> impl_; |
| 215 | + std::uint64_t room_handle_{0}; |
| 216 | + bool enabled_{false}; |
| 217 | + E2EEOptions options_; |
| 218 | + KeyProvider key_provider_; |
164 | 219 | }; |
165 | 220 |
|
166 | 221 | } // namespace livekit |
0 commit comments