You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

E2EEContext.js 6.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. /* global __filename */
  2. import { getLogger } from 'jitsi-meet-logger';
  3. const logger = getLogger(__filename);
  4. // Flag to set on senders / receivers to avoid setting up the encryption transform
  5. // more than once.
  6. const kJitsiE2EE = Symbol('kJitsiE2EE');
  7. /**
  8. * Context encapsulating the cryptography bits required for E2EE.
  9. * This uses the WebRTC Insertable Streams API which is explained in
  10. * https://github.com/alvestrand/webrtc-media-streams/blob/master/explainer.md
  11. * that provides access to the encoded frames and allows them to be transformed.
  12. *
  13. * The encoded frame format is explained below in the _encodeFunction method.
  14. * High level design goals were:
  15. * - do not require changes to existing SFUs and retain (VP8) metadata.
  16. * - allow the SFU to rewrite SSRCs, timestamp, pictureId.
  17. * - allow for the key to be rotated frequently.
  18. */
  19. export default class E2EEcontext {
  20. /**
  21. * Build a new E2EE context instance, which will be used in a given conference.
  22. *
  23. * @param {string} options.salt - Salt to be used for key deviation.
  24. * FIXME: We currently use the MUC room name for this which has the same lifetime
  25. * as this context. While not (pseudo)random as recommended in
  26. * https://developer.mozilla.org/en-US/docs/Web/API/Pbkdf2Params
  27. * this is easily available and the same for all participants.
  28. * We currently do not enforce a minimum length of 16 bytes either.
  29. */
  30. constructor(options) {
  31. this._options = options;
  32. // Determine the URL for the worker script. Relative URLs are relative to
  33. // the entry point, not the script that launches the worker.
  34. let baseUrl = '';
  35. const ljm = document.querySelector('script[src*="lib-jitsi-meet"]');
  36. if (ljm) {
  37. const idx = ljm.src.lastIndexOf('/');
  38. baseUrl = `${ljm.src.substring(0, idx)}/`;
  39. }
  40. // Initialize the E2EE worker. In order to avoid CORS issues, start the worker and have it
  41. // synchronously load the JS.
  42. const workerUrl = `${baseUrl}lib-jitsi-meet.e2ee-worker.js`;
  43. const workerBlob
  44. = new Blob([ `importScripts("${workerUrl}");` ], { type: 'application/javascript' });
  45. const blobUrl = window.URL.createObjectURL(workerBlob);
  46. this._worker = new Worker(blobUrl, { name: 'E2EE Worker' });
  47. this._worker.onerror = e => logger.onerror(e);
  48. // Initialize the salt and convert it once.
  49. const encoder = new TextEncoder();
  50. // Send initial options to worker.
  51. this._worker.postMessage({
  52. operation: 'initialize',
  53. salt: encoder.encode(options.salt)
  54. });
  55. }
  56. /**
  57. * Cleans up all state associated with the given participant. This is needed when a
  58. * participant leaves the current conference.
  59. *
  60. * @param {string} participantId - The participant that just left.
  61. */
  62. cleanup(participantId) {
  63. this._worker.postMessage({
  64. operation: 'cleanup',
  65. participantId
  66. });
  67. }
  68. /**
  69. * Handles the given {@code RTCRtpReceiver} by creating a {@code TransformStream} which will inject
  70. * a frame decoder.
  71. *
  72. * @param {RTCRtpReceiver} receiver - The receiver which will get the decoding function injected.
  73. * @param {string} kind - The kind of track this receiver belongs to.
  74. * @param {string} participantId - The participant id that this receiver belongs to.
  75. */
  76. handleReceiver(receiver, kind, participantId) {
  77. if (receiver[kJitsiE2EE]) {
  78. return;
  79. }
  80. receiver[kJitsiE2EE] = true;
  81. let receiverStreams;
  82. if (receiver.createEncodedStreams) {
  83. receiverStreams = receiver.createEncodedStreams();
  84. } else {
  85. receiverStreams = kind === 'video' ? receiver.createEncodedVideoStreams()
  86. : receiver.createEncodedAudioStreams();
  87. }
  88. this._worker.postMessage({
  89. operation: 'decode',
  90. readableStream: receiverStreams.readable || receiverStreams.readableStream,
  91. writableStream: receiverStreams.writable || receiverStreams.writableStream,
  92. participantId
  93. }, [ receiverStreams.readable || receiverStreams.readableStream,
  94. receiverStreams.writable || receiverStreams.writableStream ]);
  95. }
  96. /**
  97. * Handles the given {@code RTCRtpSender} by creating a {@code TransformStream} which will inject
  98. * a frame encoder.
  99. *
  100. * @param {RTCRtpSender} sender - The sender which will get the encoding function injected.
  101. * @param {string} kind - The kind of track this sender belongs to.
  102. * @param {string} participantId - The participant id that this sender belongs to.
  103. */
  104. handleSender(sender, kind, participantId) {
  105. if (sender[kJitsiE2EE]) {
  106. return;
  107. }
  108. sender[kJitsiE2EE] = true;
  109. let senderStreams;
  110. if (sender.createEncodedStreams) {
  111. senderStreams = sender.createEncodedStreams();
  112. } else {
  113. senderStreams = kind === 'video' ? sender.createEncodedVideoStreams()
  114. : sender.createEncodedAudioStreams();
  115. }
  116. this._worker.postMessage({
  117. operation: 'encode',
  118. readableStream: senderStreams.readable || senderStreams.readableStream,
  119. writableStream: senderStreams.writable || senderStreams.writableStream,
  120. participantId
  121. }, [ senderStreams.readable || senderStreams.readableStream,
  122. senderStreams.writable || senderStreams.writableStream ]);
  123. }
  124. /**
  125. * Set the E2EE key for the specified participant.
  126. *
  127. * @param {string} participantId - the ID of the participant who's key we are setting.
  128. * @param {Uint8Array | boolean} key - they key for the given participant.
  129. * @param {Number} keyIndex - the key index.
  130. */
  131. setKey(participantId, key, keyIndex) {
  132. this._worker.postMessage({
  133. operation: 'setKey',
  134. participantId,
  135. key,
  136. keyIndex
  137. });
  138. }
  139. }