Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

ManagedKeyHandler.js 7.0KB

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