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.

ManagedKeyHandler.js 5.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. /* global __filename */
  2. import { getLogger } from '@jitsi/logger';
  3. import debounce from 'lodash.debounce';
  4. import * as JitsiConferenceEvents from '../../JitsiConferenceEvents';
  5. import { KeyHandler } from './KeyHandler';
  6. import { OlmAdapter } from './OlmAdapter';
  7. import { importKey, ratchet } from './crypto-utils';
  8. const logger = getLogger(__filename);
  9. // Period which we'll wait before updating / rotating our keys when a participant
  10. // joins or leaves.
  11. const DEBOUNCE_PERIOD = 5000;
  12. /**
  13. * This module integrates {@link E2EEContext} with {@link OlmAdapter} in order to distribute the keys for encryption.
  14. */
  15. export class ManagedKeyHandler extends KeyHandler {
  16. /**
  17. * Build a new AutomaticKeyHandler instance, which will be used in a given conference.
  18. */
  19. constructor(conference) {
  20. super(conference);
  21. this._key = undefined;
  22. this._conferenceJoined = false;
  23. this._olmAdapter = new OlmAdapter(conference);
  24. this._rotateKey = debounce(this._rotateKeyImpl, DEBOUNCE_PERIOD);
  25. this._ratchetKey = debounce(this._ratchetKeyImpl, DEBOUNCE_PERIOD);
  26. // Olm signalling events.
  27. this._olmAdapter.on(
  28. OlmAdapter.events.PARTICIPANT_KEY_UPDATED,
  29. this._onParticipantKeyUpdated.bind(this));
  30. this.conference.on(
  31. JitsiConferenceEvents.PARTICIPANT_PROPERTY_CHANGED,
  32. this._onParticipantPropertyChanged.bind(this));
  33. this.conference.on(
  34. JitsiConferenceEvents.USER_JOINED,
  35. this._onParticipantJoined.bind(this));
  36. this.conference.on(
  37. JitsiConferenceEvents.USER_LEFT,
  38. this._onParticipantLeft.bind(this));
  39. this.conference.on(
  40. JitsiConferenceEvents.CONFERENCE_JOINED,
  41. () => {
  42. this._conferenceJoined = true;
  43. });
  44. }
  45. /**
  46. * When E2EE is enabled it initializes sessions and sets the key.
  47. * Cleans up the sessions when disabled.
  48. *
  49. * @param {boolean} enabled - whether E2EE should be enabled or not.
  50. * @returns {void}
  51. */
  52. async _setEnabled(enabled) {
  53. if (enabled) {
  54. await this._olmAdapter.initSessions();
  55. } else {
  56. this._olmAdapter.clearAllParticipantsSessions();
  57. }
  58. // Generate a random key in case we are enabling.
  59. this._key = enabled ? this._generateKey() : false;
  60. // Send it to others using the E2EE olm channel.
  61. const index = await this._olmAdapter.updateKey(this._key);
  62. // Set our key so we begin encrypting.
  63. this.e2eeCtx.setKey(this.conference.myUserId(), this._key, index);
  64. }
  65. /**
  66. * Handles an update in a participant's presence property.
  67. *
  68. * @param {JitsiParticipant} participant - The participant.
  69. * @param {string} name - The name of the property that changed.
  70. * @param {*} oldValue - The property's previous value.
  71. * @param {*} newValue - The property's new value.
  72. * @private
  73. */
  74. async _onParticipantPropertyChanged(participant, name, oldValue, newValue) {
  75. switch (name) {
  76. case 'e2ee.idKey':
  77. logger.debug(`Participant ${participant.getId()} updated their id key: ${newValue}`);
  78. break;
  79. case 'e2ee.enabled':
  80. if (!newValue && this.enabled) {
  81. this._olmAdapter.clearParticipantSession(participant);
  82. }
  83. break;
  84. }
  85. }
  86. /**
  87. * Advances (using ratcheting) the current key when a new participant joins the conference.
  88. * @private
  89. */
  90. _onParticipantJoined() {
  91. if (this._conferenceJoined && this.enabled) {
  92. this._ratchetKey();
  93. }
  94. }
  95. /**
  96. * Rotates the current key when a participant leaves the conference.
  97. * @private
  98. */
  99. _onParticipantLeft(id) {
  100. this.e2eeCtx.cleanup(id);
  101. if (this.enabled) {
  102. this._rotateKey();
  103. }
  104. }
  105. /**
  106. * Rotates the local key. Rotating the key implies creating a new one, then distributing it
  107. * to all participants and once they all received it, start using it.
  108. *
  109. * @private
  110. */
  111. async _rotateKeyImpl() {
  112. logger.debug('Rotating key');
  113. this._key = this._generateKey();
  114. const index = await this._olmAdapter.updateKey(this._key);
  115. this.e2eeCtx.setKey(this.conference.myUserId(), this._key, index);
  116. }
  117. /**
  118. * Advances the current key by using ratcheting.
  119. *
  120. * @private
  121. */
  122. async _ratchetKeyImpl() {
  123. logger.debug('Ratchetting key');
  124. const material = await importKey(this._key);
  125. const newKey = await ratchet(material);
  126. this._key = new Uint8Array(newKey);
  127. const index = this._olmAdapter.updateCurrentKey(this._key);
  128. this.e2eeCtx.setKey(this.conference.myUserId(), this._key, index);
  129. }
  130. /**
  131. * Handles an update in a participant's key.
  132. *
  133. * @param {string} id - The participant ID.
  134. * @param {Uint8Array | boolean} key - The new key for the participant.
  135. * @param {Number} index - The new key's index.
  136. * @private
  137. */
  138. _onParticipantKeyUpdated(id, key, index) {
  139. logger.debug(`Participant ${id} updated their key`);
  140. this.e2eeCtx.setKey(id, key, index);
  141. }
  142. /**
  143. * Generates a new 256 bit random key.
  144. *
  145. * @returns {Uint8Array}
  146. * @private
  147. */
  148. _generateKey() {
  149. return window.crypto.getRandomValues(new Uint8Array(32));
  150. }
  151. }