livekit-client 1.13.3 → 1.13.4
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +2 -2
- package/dist/livekit-client.e2ee.worker.js +1 -1
- package/dist/livekit-client.e2ee.worker.js.map +1 -1
- package/dist/livekit-client.e2ee.worker.mjs +86 -16
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +87 -8
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.umd.js +1 -1
- package/dist/livekit-client.umd.js.map +1 -1
- package/dist/src/e2ee/utils.d.ts +3 -0
- package/dist/src/e2ee/utils.d.ts.map +1 -1
- package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/track/LocalTrack.d.ts +7 -0
- package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
- package/dist/src/room/track/RemoteTrack.d.ts +7 -0
- package/dist/src/room/track/RemoteTrack.d.ts.map +1 -1
- package/dist/ts4.2/src/e2ee/utils.d.ts +3 -0
- package/dist/ts4.2/src/room/track/LocalTrack.d.ts +7 -0
- package/dist/ts4.2/src/room/track/RemoteTrack.d.ts +7 -0
- package/package.json +1 -1
- package/src/e2ee/utils.ts +52 -0
- package/src/e2ee/worker/FrameCryptor.ts +45 -27
- package/src/room/RTCEngine.ts +3 -2
- package/src/room/Room.ts +3 -3
- package/src/room/participant/LocalParticipant.ts +2 -2
- package/src/room/track/LocalTrack.ts +14 -0
- package/src/room/track/RemoteTrack.ts +14 -0
@@ -882,6 +882,54 @@ function ratchet(material, salt) {
|
|
882
882
|
return crypto.subtle.deriveBits(algorithmOptions, material, 256);
|
883
883
|
});
|
884
884
|
}
|
885
|
+
function needsRbspUnescaping(frameData) {
|
886
|
+
for (var i = 0; i < frameData.length - 3; i++) {
|
887
|
+
if (frameData[i] == 0 && frameData[i + 1] == 0 && frameData[i + 2] == 3) return true;
|
888
|
+
}
|
889
|
+
return false;
|
890
|
+
}
|
891
|
+
function parseRbsp(stream) {
|
892
|
+
const dataOut = [];
|
893
|
+
var length = stream.length;
|
894
|
+
for (var i = 0; i < stream.length;) {
|
895
|
+
// Be careful about over/underflow here. byte_length_ - 3 can underflow, and
|
896
|
+
// i + 3 can overflow, but byte_length_ - i can't, because i < byte_length_
|
897
|
+
// above, and that expression will produce the number of bytes left in
|
898
|
+
// the stream including the byte at i.
|
899
|
+
if (length - i >= 3 && !stream[i] && !stream[i + 1] && stream[i + 2] == 3) {
|
900
|
+
// Two rbsp bytes.
|
901
|
+
dataOut.push(stream[i++]);
|
902
|
+
dataOut.push(stream[i++]);
|
903
|
+
// Skip the emulation byte.
|
904
|
+
i++;
|
905
|
+
} else {
|
906
|
+
// Single rbsp byte.
|
907
|
+
dataOut.push(stream[i++]);
|
908
|
+
}
|
909
|
+
}
|
910
|
+
return new Uint8Array(dataOut);
|
911
|
+
}
|
912
|
+
const kZerosInStartSequence = 2;
|
913
|
+
const kEmulationByte = 3;
|
914
|
+
function writeRbsp(data_in) {
|
915
|
+
const dataOut = [];
|
916
|
+
var numConsecutiveZeros = 0;
|
917
|
+
for (var i = 0; i < data_in.length; ++i) {
|
918
|
+
var byte = data_in[i];
|
919
|
+
if (byte <= kEmulationByte && numConsecutiveZeros >= kZerosInStartSequence) {
|
920
|
+
// Need to escape.
|
921
|
+
dataOut.push(kEmulationByte);
|
922
|
+
numConsecutiveZeros = 0;
|
923
|
+
}
|
924
|
+
dataOut.push(byte);
|
925
|
+
if (byte == 0) {
|
926
|
+
++numConsecutiveZeros;
|
927
|
+
} else {
|
928
|
+
numConsecutiveZeros = 0;
|
929
|
+
}
|
930
|
+
}
|
931
|
+
return new Uint8Array(dataOut);
|
932
|
+
}
|
885
933
|
|
886
934
|
class SifGuard {
|
887
935
|
constructor() {
|
@@ -1045,8 +1093,9 @@ class FrameCryptor extends BaseFrameCryptor {
|
|
1045
1093
|
const keyIndex = this.keys.getCurrentKeyIndex();
|
1046
1094
|
if (encryptionKey) {
|
1047
1095
|
const iv = this.makeIV((_a = encodedFrame.getMetadata().synchronizationSource) !== null && _a !== void 0 ? _a : -1, encodedFrame.timestamp);
|
1096
|
+
let frameInfo = this.getUnencryptedBytes(encodedFrame);
|
1048
1097
|
// Thіs is not encrypted and contains the VP8 payload descriptor or the Opus TOC byte.
|
1049
|
-
const frameHeader = new Uint8Array(encodedFrame.data, 0,
|
1098
|
+
const frameHeader = new Uint8Array(encodedFrame.data, 0, frameInfo.unencryptedBytes);
|
1050
1099
|
// Frame trailer contains the R|IV_LENGTH and key index
|
1051
1100
|
const frameTrailer = new Uint8Array(2);
|
1052
1101
|
frameTrailer[0] = IV_LENGTH;
|
@@ -1063,14 +1112,18 @@ class FrameCryptor extends BaseFrameCryptor {
|
|
1063
1112
|
name: ENCRYPTION_ALGORITHM,
|
1064
1113
|
iv,
|
1065
1114
|
additionalData: new Uint8Array(encodedFrame.data, 0, frameHeader.byteLength)
|
1066
|
-
}, encryptionKey, new Uint8Array(encodedFrame.data,
|
1067
|
-
|
1068
|
-
|
1069
|
-
|
1070
|
-
|
1071
|
-
|
1072
|
-
|
1073
|
-
|
1115
|
+
}, encryptionKey, new Uint8Array(encodedFrame.data, frameInfo.unencryptedBytes));
|
1116
|
+
let newDataWithoutHeader = new Uint8Array(cipherText.byteLength + iv.byteLength + frameTrailer.byteLength);
|
1117
|
+
newDataWithoutHeader.set(new Uint8Array(cipherText)); // add ciphertext.
|
1118
|
+
newDataWithoutHeader.set(new Uint8Array(iv), cipherText.byteLength); // append IV.
|
1119
|
+
newDataWithoutHeader.set(frameTrailer, cipherText.byteLength + iv.byteLength); // append frame trailer.
|
1120
|
+
if (frameInfo.isH264) {
|
1121
|
+
newDataWithoutHeader = writeRbsp(newDataWithoutHeader);
|
1122
|
+
}
|
1123
|
+
var newData = new Uint8Array(frameHeader.byteLength + newDataWithoutHeader.byteLength);
|
1124
|
+
newData.set(frameHeader);
|
1125
|
+
newData.set(newDataWithoutHeader, frameHeader.byteLength);
|
1126
|
+
encodedFrame.data = newData.buffer;
|
1074
1127
|
return controller.enqueue(encodedFrame);
|
1075
1128
|
} catch (e) {
|
1076
1129
|
// TODO: surface this to the app.
|
@@ -1150,6 +1203,7 @@ class FrameCryptor extends BaseFrameCryptor {
|
|
1150
1203
|
if (!ratchetOpts.encryptionKey && !keySet) {
|
1151
1204
|
throw new TypeError("no encryption key found for decryption of ".concat(this.participantIdentity));
|
1152
1205
|
}
|
1206
|
+
let frameInfo = this.getUnencryptedBytes(encodedFrame);
|
1153
1207
|
// Construct frame trailer. Similar to the frame header described in
|
1154
1208
|
// https://tools.ietf.org/html/draft-omara-sframe-00#section-4.2
|
1155
1209
|
// but we put it at the end.
|
@@ -1158,7 +1212,15 @@ class FrameCryptor extends BaseFrameCryptor {
|
|
1158
1212
|
// payload |IV...(length = IV_LENGTH)|R|IV_LENGTH|KID |
|
1159
1213
|
// ---------+-------------------------+-+---------+----
|
1160
1214
|
try {
|
1161
|
-
const frameHeader = new Uint8Array(encodedFrame.data, 0,
|
1215
|
+
const frameHeader = new Uint8Array(encodedFrame.data, 0, frameInfo.unencryptedBytes);
|
1216
|
+
var encryptedData = new Uint8Array(encodedFrame.data, frameHeader.length, encodedFrame.data.byteLength - frameHeader.length);
|
1217
|
+
if (frameInfo.isH264 && needsRbspUnescaping(encryptedData)) {
|
1218
|
+
encryptedData = parseRbsp(encryptedData);
|
1219
|
+
const newUint8 = new Uint8Array(frameHeader.byteLength + encryptedData.byteLength);
|
1220
|
+
newUint8.set(frameHeader);
|
1221
|
+
newUint8.set(encryptedData, frameHeader.byteLength);
|
1222
|
+
encodedFrame.data = newUint8.buffer;
|
1223
|
+
}
|
1162
1224
|
const frameTrailer = new Uint8Array(encodedFrame.data, encodedFrame.data.byteLength - 2, 2);
|
1163
1225
|
const ivLength = frameTrailer[0];
|
1164
1226
|
const iv = new Uint8Array(encodedFrame.data, encodedFrame.data.byteLength - ivLength - frameTrailer.byteLength, ivLength);
|
@@ -1253,26 +1315,32 @@ class FrameCryptor extends BaseFrameCryptor {
|
|
1253
1315
|
}
|
1254
1316
|
getUnencryptedBytes(frame) {
|
1255
1317
|
var _a;
|
1318
|
+
var frameInfo = {
|
1319
|
+
unencryptedBytes: 0,
|
1320
|
+
isH264: false
|
1321
|
+
};
|
1256
1322
|
if (isVideoFrame(frame)) {
|
1257
1323
|
let detectedCodec = (_a = this.getVideoCodec(frame)) !== null && _a !== void 0 ? _a : this.videoCodec;
|
1258
1324
|
if (detectedCodec === 'av1' || detectedCodec === 'vp9') {
|
1259
1325
|
throw new Error("".concat(detectedCodec, " is not yet supported for end to end encryption"));
|
1260
1326
|
}
|
1261
1327
|
if (detectedCodec === 'vp8') {
|
1262
|
-
|
1328
|
+
frameInfo.unencryptedBytes = UNENCRYPTED_BYTES[frame.type];
|
1329
|
+
return frameInfo;
|
1263
1330
|
}
|
1264
1331
|
const data = new Uint8Array(frame.data);
|
1265
1332
|
try {
|
1266
1333
|
const naluIndices = findNALUIndices(data);
|
1267
1334
|
// if the detected codec is undefined we test whether it _looks_ like a h264 frame as a best guess
|
1268
|
-
|
1269
|
-
if (isH264) {
|
1335
|
+
frameInfo.isH264 = detectedCodec === 'h264' || naluIndices.some(naluIndex => [NALUType.SLICE_IDR, NALUType.SLICE_NON_IDR].includes(parseNALUType(data[naluIndex])));
|
1336
|
+
if (frameInfo.isH264) {
|
1270
1337
|
for (const index of naluIndices) {
|
1271
1338
|
let type = parseNALUType(data[index]);
|
1272
1339
|
switch (type) {
|
1273
1340
|
case NALUType.SLICE_IDR:
|
1274
1341
|
case NALUType.SLICE_NON_IDR:
|
1275
|
-
|
1342
|
+
frameInfo.unencryptedBytes = index + 2;
|
1343
|
+
return frameInfo;
|
1276
1344
|
default:
|
1277
1345
|
break;
|
1278
1346
|
}
|
@@ -1282,9 +1350,11 @@ class FrameCryptor extends BaseFrameCryptor {
|
|
1282
1350
|
} catch (e) {
|
1283
1351
|
// no op, we just continue and fallback to vp8
|
1284
1352
|
}
|
1285
|
-
|
1353
|
+
frameInfo.unencryptedBytes = UNENCRYPTED_BYTES[frame.type];
|
1354
|
+
return frameInfo;
|
1286
1355
|
} else {
|
1287
|
-
|
1356
|
+
frameInfo.unencryptedBytes = UNENCRYPTED_BYTES.audio;
|
1357
|
+
return frameInfo;
|
1288
1358
|
}
|
1289
1359
|
}
|
1290
1360
|
/**
|