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 4.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. /* global __filename */
  2. import { createWorkerScript } from './Worker';
  3. import { getLogger } from 'jitsi-meet-logger';
  4. const logger = getLogger(__filename);
  5. // Flag to set on senders / receivers to avoid setting up the encryption transform
  6. // more than once.
  7. const kJitsiE2EE = Symbol('kJitsiE2EE');
  8. /**
  9. * Context encapsulating the cryptography bits required for E2EE.
  10. * This uses the WebRTC Insertable Streams API which is explained in
  11. * https://github.com/alvestrand/webrtc-media-streams/blob/master/explainer.md
  12. * that provides access to the encoded frames and allows them to be transformed.
  13. *
  14. * The encoded frame format is explained below in the _encodeFunction method.
  15. * High level design goals were:
  16. * - do not require changes to existing SFUs and retain (VP8) metadata.
  17. * - allow the SFU to rewrite SSRCs, timestamp, pictureId.
  18. * - allow for the key to be rotated frequently.
  19. */
  20. export default class E2EEcontext {
  21. /**
  22. * Build a new E2EE context instance, which will be used in a given conference.
  23. *
  24. * @param {string} options.salt - Salt to be used for key deviation.
  25. * FIXME: We currently use the MUC room name for this which has the same lifetime
  26. * as this context. While not (pseudo)random as recommended in
  27. * https://developer.mozilla.org/en-US/docs/Web/API/Pbkdf2Params
  28. * this is easily available and the same for all participants.
  29. * We currently do not enforce a minimum length of 16 bytes either.
  30. */
  31. constructor(options) {
  32. this._options = options;
  33. // Initialize the E2EE worker.
  34. this._worker = new Worker(createWorkerScript(), {
  35. name: 'E2EE Worker'
  36. });
  37. this._worker.onerror = e => logger.onerror(e);
  38. // Initialize the salt and convert it once.
  39. const encoder = new TextEncoder();
  40. // Send initial options to worker.
  41. this._worker.postMessage({
  42. operation: 'initialize',
  43. salt: encoder.encode(options.salt)
  44. });
  45. }
  46. /**
  47. * Handles the given {@code RTCRtpReceiver} by creating a {@code TransformStream} which will inject
  48. * a frame decoder.
  49. *
  50. * @param {RTCRtpReceiver} receiver - The receiver which will get the decoding function injected.
  51. * @param {string} kind - The kind of track this receiver belongs to.
  52. * @param {string} participantId - The participant id that this receiver belongs to.
  53. */
  54. handleReceiver(receiver, kind, participantId) {
  55. if (receiver[kJitsiE2EE]) {
  56. return;
  57. }
  58. receiver[kJitsiE2EE] = true;
  59. let receiverStreams;
  60. if (receiver.createEncodedStreams) {
  61. receiverStreams = receiver.createEncodedStreams();
  62. } else {
  63. receiverStreams = kind === 'video' ? receiver.createEncodedVideoStreams()
  64. : receiver.createEncodedAudioStreams();
  65. }
  66. this._worker.postMessage({
  67. operation: 'decode',
  68. readableStream: receiverStreams.readableStream,
  69. writableStream: receiverStreams.writableStream,
  70. participantId
  71. }, [ receiverStreams.readableStream, receiverStreams.writableStream ]);
  72. }
  73. /**
  74. * Handles the given {@code RTCRtpSender} by creating a {@code TransformStream} which will inject
  75. * a frame encoder.
  76. *
  77. * @param {RTCRtpSender} sender - The sender which will get the encoding function injected.
  78. * @param {string} kind - The kind of track this sender belongs to.
  79. * @param {string} participantId - The participant id that this sender belongs to.
  80. */
  81. handleSender(sender, kind, participantId) {
  82. if (sender[kJitsiE2EE]) {
  83. return;
  84. }
  85. sender[kJitsiE2EE] = true;
  86. let senderStreams;
  87. if (sender.createEncodedStreams) {
  88. senderStreams = sender.createEncodedStreams();
  89. } else {
  90. senderStreams = kind === 'video' ? sender.createEncodedVideoStreams()
  91. : sender.createEncodedAudioStreams();
  92. }
  93. this._worker.postMessage({
  94. operation: 'encode',
  95. readableStream: senderStreams.readableStream,
  96. writableStream: senderStreams.writableStream,
  97. participantId
  98. }, [ senderStreams.readableStream, senderStreams.writableStream ]);
  99. }
  100. /**
  101. * Sets the key to be used for E2EE.
  102. *
  103. * @param {string} value - Value to be used as the new key. May be falsy to disable end-to-end encryption.
  104. */
  105. setKey(value) {
  106. let key;
  107. if (value) {
  108. const encoder = new TextEncoder();
  109. key = encoder.encode(value);
  110. } else {
  111. key = false;
  112. }
  113. this._worker.postMessage({
  114. operation: 'setKey',
  115. key
  116. });
  117. }
  118. }