modified lib-jitsi-meet dev repo
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. /* eslint-disable no-bitwise */
  2. /* global BigInt */
  3. import { deriveKeys, importKey, ratchet } from './crypto-utils';
  4. // We use a ringbuffer of keys so we can change them and still decode packets that were
  5. // encrypted with an old key. We use a size of 16 which corresponds to the four bits
  6. // in the frame trailer.
  7. const KEYRING_SIZE = 16;
  8. // We copy the first bytes of the VP8 payload unencrypted.
  9. // For keyframes this is 10 bytes, for non-keyframes (delta) 3. See
  10. // https://tools.ietf.org/html/rfc6386#section-9.1
  11. // This allows the bridge to continue detecting keyframes (only one byte needed in the JVB)
  12. // and is also a bit easier for the VP8 decoder (i.e. it generates funny garbage pictures
  13. // instead of being unable to decode).
  14. // This is a bit for show and we might want to reduce to 1 unconditionally in the final version.
  15. //
  16. // For audio (where frame.type is not set) we do not encrypt the opus TOC byte:
  17. // https://tools.ietf.org/html/rfc6716#section-3.1
  18. const UNENCRYPTED_BYTES = {
  19. key: 10,
  20. delta: 3,
  21. undefined: 1 // frame.type is not set on audio
  22. };
  23. const ENCRYPTION_ALGORITHM = 'AES-GCM';
  24. /* We use a 96 bit IV for AES GCM. This is signalled in plain together with the
  25. packet. See https://developer.mozilla.org/en-US/docs/Web/API/AesGcmParams */
  26. const IV_LENGTH = 12;
  27. const RATCHET_WINDOW_SIZE = 8;
  28. /**
  29. * Per-participant context holding the cryptographic keys and
  30. * encode/decode functions
  31. */
  32. export class Context {
  33. /**
  34. * @param {string} id - local muc resourcepart
  35. */
  36. constructor(id) {
  37. // An array (ring) of keys that we use for sending and receiving.
  38. this._cryptoKeyRing = new Array(KEYRING_SIZE);
  39. // A pointer to the currently used key.
  40. this._currentKeyIndex = -1;
  41. this._sendCounts = new Map();
  42. this._id = id;
  43. }
  44. /**
  45. * Derives the different subkeys and starts using them for encryption or
  46. * decryption.
  47. * @param {Uint8Array|false} key bytes. Pass false to disable.
  48. * @param {Number} keyIndex
  49. */
  50. async setKey(keyBytes, keyIndex) {
  51. let newKey;
  52. if (keyBytes) {
  53. const material = await importKey(keyBytes);
  54. newKey = await deriveKeys(material);
  55. } else {
  56. newKey = false;
  57. }
  58. this._currentKeyIndex = keyIndex % this._cryptoKeyRing.length;
  59. this._setKeys(newKey);
  60. }
  61. /**
  62. * Sets a set of keys and resets the sendCount.
  63. * decryption.
  64. * @param {Object} keys set of keys.
  65. * @param {Number} keyIndex optional
  66. * @private
  67. */
  68. _setKeys(keys, keyIndex = -1) {
  69. if (keyIndex >= 0) {
  70. this._cryptoKeyRing[keyIndex] = keys;
  71. } else {
  72. this._cryptoKeyRing[this._currentKeyIndex] = keys;
  73. }
  74. this._sendCount = BigInt(0); // eslint-disable-line new-cap
  75. }
  76. /**
  77. * Function that will be injected in a stream and will encrypt the given encoded frames.
  78. *
  79. * @param {RTCEncodedVideoFrame|RTCEncodedAudioFrame} encodedFrame - Encoded video frame.
  80. * @param {TransformStreamDefaultController} controller - TransportStreamController.
  81. *
  82. * The VP8 payload descriptor described in
  83. * https://tools.ietf.org/html/rfc7741#section-4.2
  84. * is part of the RTP packet and not part of the frame and is not controllable by us.
  85. * This is fine as the SFU keeps having access to it for routing.
  86. *
  87. * The encrypted frame is formed as follows:
  88. * 1) Leave the first (10, 3, 1) bytes unencrypted, depending on the frame type and kind.
  89. * 2) Form the GCM IV for the frame as described above.
  90. * 3) Encrypt the rest of the frame using AES-GCM.
  91. * 4) Allocate space for the encrypted frame.
  92. * 5) Copy the unencrypted bytes to the start of the encrypted frame.
  93. * 6) Append the ciphertext to the encrypted frame.
  94. * 7) Append the IV.
  95. * 8) Append a single byte for the key identifier.
  96. * 9) Enqueue the encrypted frame for sending.
  97. */
  98. encodeFunction(encodedFrame, controller) {
  99. const keyIndex = this._currentKeyIndex;
  100. if (this._cryptoKeyRing[keyIndex]) {
  101. const iv = this._makeIV(encodedFrame.getMetadata().synchronizationSource, encodedFrame.timestamp);
  102. // Thіs is not encrypted and contains the VP8 payload descriptor or the Opus TOC byte.
  103. const frameHeader = new Uint8Array(encodedFrame.data, 0, UNENCRYPTED_BYTES[encodedFrame.type]);
  104. // Frame trailer contains the R|IV_LENGTH and key index
  105. const frameTrailer = new Uint8Array(2);
  106. frameTrailer[0] = IV_LENGTH;
  107. frameTrailer[1] = keyIndex;
  108. // Construct frame trailer. Similar to the frame header described in
  109. // https://tools.ietf.org/html/draft-omara-sframe-00#section-4.2
  110. // but we put it at the end.
  111. //
  112. // ---------+-------------------------+-+---------+----
  113. // payload |IV...(length = IV_LENGTH)|R|IV_LENGTH|KID |
  114. // ---------+-------------------------+-+---------+----
  115. return crypto.subtle.encrypt({
  116. name: ENCRYPTION_ALGORITHM,
  117. iv,
  118. additionalData: new Uint8Array(encodedFrame.data, 0, frameHeader.byteLength)
  119. }, this._cryptoKeyRing[keyIndex].encryptionKey, new Uint8Array(encodedFrame.data,
  120. UNENCRYPTED_BYTES[encodedFrame.type]))
  121. .then(cipherText => {
  122. const newData = new ArrayBuffer(frameHeader.byteLength + cipherText.byteLength
  123. + iv.byteLength + frameTrailer.byteLength);
  124. const newUint8 = new Uint8Array(newData);
  125. newUint8.set(frameHeader); // copy first bytes.
  126. newUint8.set(
  127. new Uint8Array(cipherText), frameHeader.byteLength); // add ciphertext.
  128. newUint8.set(
  129. new Uint8Array(iv), frameHeader.byteLength + cipherText.byteLength); // append IV.
  130. newUint8.set(
  131. frameTrailer,
  132. frameHeader.byteLength + cipherText.byteLength + iv.byteLength); // append frame trailer.
  133. encodedFrame.data = newData;
  134. return controller.enqueue(encodedFrame);
  135. }, e => {
  136. // TODO: surface this to the app.
  137. console.error(e);
  138. // We are not enqueuing the frame here on purpose.
  139. });
  140. }
  141. /* NOTE WELL:
  142. * This will send unencrypted data (only protected by DTLS transport encryption) when no key is configured.
  143. * This is ok for demo purposes but should not be done once this becomes more relied upon.
  144. */
  145. controller.enqueue(encodedFrame);
  146. }
  147. /**
  148. * Function that will be injected in a stream and will decrypt the given encoded frames.
  149. *
  150. * @param {RTCEncodedVideoFrame|RTCEncodedAudioFrame} encodedFrame - Encoded video frame.
  151. * @param {TransformStreamDefaultController} controller - TransportStreamController.
  152. */
  153. async decodeFunction(encodedFrame, controller) {
  154. const data = new Uint8Array(encodedFrame.data);
  155. const keyIndex = data[encodedFrame.data.byteLength - 1];
  156. if (this._cryptoKeyRing[keyIndex]) {
  157. const decodedFrame = await this._decryptFrame(
  158. encodedFrame,
  159. keyIndex);
  160. return controller.enqueue(decodedFrame);
  161. }
  162. // TODO: this just passes through to the decoder. Is that ok? If we don't know the key yet
  163. // we might want to buffer a bit but it is still unclear how to do that (and for how long etc).
  164. controller.enqueue(encodedFrame);
  165. }
  166. /**
  167. * Function that will decrypt the given encoded frame. If the decryption fails, it will
  168. * ratchet the key for up to RATCHET_WINDOW_SIZE times.
  169. *
  170. * @param {RTCEncodedVideoFrame|RTCEncodedAudioFrame} encodedFrame - Encoded video frame.
  171. * @param {number} keyIndex - the index of the decryption data in _cryptoKeyRing array.
  172. * @param {number} ratchetCount - the number of retries after ratcheting the key.
  173. * @returns {RTCEncodedVideoFrame|RTCEncodedAudioFrame} - The decrypted frame.
  174. * @private
  175. */
  176. async _decryptFrame(
  177. encodedFrame,
  178. keyIndex,
  179. initialKey = undefined,
  180. ratchetCount = 0) {
  181. const { encryptionKey } = this._cryptoKeyRing[keyIndex];
  182. let { material } = this._cryptoKeyRing[keyIndex];
  183. // Construct frame trailer. Similar to the frame header described in
  184. // https://tools.ietf.org/html/draft-omara-sframe-00#section-4.2
  185. // but we put it at the end.
  186. //
  187. // ---------+-------------------------+-+---------+----
  188. // payload |IV...(length = IV_LENGTH)|R|IV_LENGTH|KID |
  189. // ---------+-------------------------+-+---------+----
  190. try {
  191. const frameHeader = new Uint8Array(encodedFrame.data, 0, UNENCRYPTED_BYTES[encodedFrame.type]);
  192. const frameTrailer = new Uint8Array(encodedFrame.data, encodedFrame.data.byteLength - 2, 2);
  193. const ivLength = frameTrailer[0];
  194. const iv = new Uint8Array(
  195. encodedFrame.data,
  196. encodedFrame.data.byteLength - ivLength - frameTrailer.byteLength,
  197. ivLength);
  198. const cipherTextStart = frameHeader.byteLength;
  199. const cipherTextLength = encodedFrame.data.byteLength
  200. - (frameHeader.byteLength + ivLength + frameTrailer.byteLength);
  201. const plainText = await crypto.subtle.decrypt({
  202. name: 'AES-GCM',
  203. iv,
  204. additionalData: new Uint8Array(encodedFrame.data, 0, frameHeader.byteLength)
  205. },
  206. encryptionKey,
  207. new Uint8Array(encodedFrame.data, cipherTextStart, cipherTextLength));
  208. const newData = new ArrayBuffer(frameHeader.byteLength + plainText.byteLength);
  209. const newUint8 = new Uint8Array(newData);
  210. newUint8.set(new Uint8Array(encodedFrame.data, 0, frameHeader.byteLength));
  211. newUint8.set(new Uint8Array(plainText), frameHeader.byteLength);
  212. encodedFrame.data = newData;
  213. } catch (error) {
  214. if (ratchetCount < RATCHET_WINDOW_SIZE) {
  215. material = await importKey(await ratchet(material));
  216. const newKey = await deriveKeys(material);
  217. this._setKeys(newKey);
  218. return await this._decryptFrame(
  219. encodedFrame,
  220. keyIndex,
  221. initialKey || this._cryptoKeyRing[this._currentKeyIndex],
  222. ratchetCount + 1);
  223. }
  224. /*
  225. Since the key it is first send and only afterwards actually used for encrypting, there were
  226. situations when the decrypting failed due to the fact that the received frame was not encrypted
  227. yet and ratcheting, of course, did not solve the problem. So if we fail RATCHET_WINDOW_SIZE times,
  228. we come back to the initial key.
  229. */
  230. this._setKeys(initialKey);
  231. // TODO: notify the application about error status.
  232. }
  233. return encodedFrame;
  234. }
  235. /**
  236. * Construct the IV used for AES-GCM and sent (in plain) with the packet similar to
  237. * https://tools.ietf.org/html/rfc7714#section-8.1
  238. * It concatenates
  239. * - the 32 bit synchronization source (SSRC) given on the encoded frame,
  240. * - the 32 bit rtp timestamp given on the encoded frame,
  241. * - a send counter that is specific to the SSRC. Starts at a random number.
  242. * The send counter is essentially the pictureId but we currently have to implement this ourselves.
  243. * There is no XOR with a salt. Note that this IV leaks the SSRC to the receiver but since this is
  244. * randomly generated and SFUs may not rewrite this is considered acceptable.
  245. * The SSRC is used to allow demultiplexing multiple streams with the same key, as described in
  246. * https://tools.ietf.org/html/rfc3711#section-4.1.1
  247. * The RTP timestamp is 32 bits and advances by the codec clock rate (90khz for video, 48khz for
  248. * opus audio) every second. For video it rolls over roughly every 13 hours.
  249. * The send counter will advance at the frame rate (30fps for video, 50fps for 20ms opus audio)
  250. * every second. It will take a long time to roll over.
  251. *
  252. * See also https://developer.mozilla.org/en-US/docs/Web/API/AesGcmParams
  253. */
  254. _makeIV(synchronizationSource, timestamp) {
  255. const iv = new ArrayBuffer(IV_LENGTH);
  256. const ivView = new DataView(iv);
  257. // having to keep our own send count (similar to a picture id) is not ideal.
  258. if (!this._sendCounts.has(synchronizationSource)) {
  259. // Initialize with a random offset, similar to the RTP sequence number.
  260. this._sendCounts.set(synchronizationSource, Math.floor(Math.random() * 0xFFFF));
  261. }
  262. const sendCount = this._sendCounts.get(synchronizationSource);
  263. ivView.setUint32(0, synchronizationSource);
  264. ivView.setUint32(4, timestamp);
  265. ivView.setUint32(8, sendCount % 0xFFFF);
  266. this._sendCounts.set(synchronizationSource, sendCount + 1);
  267. return iv;
  268. }
  269. }