{"size": 5512, "ext": "js", "max_stars_repo_path": "modules/e2ee/ManagedKeyHandler.js", "max_stars_repo_name": "nils-ohlmeier/lib-jitsi-meet", "max_stars_repo_stars_event_min_datetime": "2016-02-03T11:22:03.000Z", "max_stars_repo_stars_event_max_datetime": "2022-03-24T09:25:23.000Z", "max_issues_repo_path": "modules/e2ee/ManagedKeyHandler.js", "max_issues_repo_name": "futurianh1k/lib-jitsi-meet", "max_issues_repo_issues_event_min_datetime": "2016-02-02T15:08:04.000Z", "max_issues_repo_issues_event_max_datetime": "2022-03-31T21:27:24.000Z", "max_forks_repo_path": "modules/e2ee/ManagedKeyHandler.js", "max_forks_repo_name": "futurianh1k/lib-jitsi-meet", "max_forks_repo_forks_event_min_datetime": "2016-01-29T11:09:07.000Z", "max_forks_repo_forks_event_max_datetime": "2022-03-30T20:13:45.000Z"}
{"max_stars_count": 1202, "max_issues_count": 1036, "max_forks_count": 1146, "avg_line_length": 30.6222222222, "max_line_length": 117, "alphanum_fraction": 0.6175616836}
import { getLogger } from '@jitsi/logger';
import debounce from 'lodash.debounce';

import * as JitsiConferenceEvents from '../../JitsiConferenceEvents';

import { KeyHandler } from './KeyHandler';
import { OlmAdapter } from './OlmAdapter';
import { importKey, ratchet } from './crypto-utils';

const logger = getLogger(__filename);

// Period which we'll wait before updating / rotating our keys when a participant
// joins or leaves.
const DEBOUNCE_PERIOD = 5000;

/**
 * This module integrates {@link E2EEContext} with {@link OlmAdapter} in order to distribute the keys for encryption.
 */
export class ManagedKeyHandler extends KeyHandler {
    /**
     * Build a new AutomaticKeyHandler instance, which will be used in a given conference.
     */
    constructor(conference) {
        super(conference);

        this._key = undefined;
        this._conferenceJoined = false;

        this._olmAdapter = new OlmAdapter(conference);

        this._rotateKey = debounce(this._rotateKeyImpl, DEBOUNCE_PERIOD);
        this._ratchetKey = debounce(this._ratchetKeyImpl, DEBOUNCE_PERIOD);

        // Olm signalling events.
        this._olmAdapter.on(
            OlmAdapter.events.PARTICIPANT_KEY_UPDATED,
            this._onParticipantKeyUpdated.bind(this));

        this.conference.on(
            JitsiConferenceEvents.PARTICIPANT_PROPERTY_CHANGED,
            this._onParticipantPropertyChanged.bind(this));
        this.conference.on(
            JitsiConferenceEvents.USER_JOINED,
            this._onParticipantJoined.bind(this));
        this.conference.on(
            JitsiConferenceEvents.USER_LEFT,
            this._onParticipantLeft.bind(this));
        this.conference.on(
                JitsiConferenceEvents.CONFERENCE_JOINED,
                () => {
                    this._conferenceJoined = true;
                });
    }

    /**
     * When E2EE is enabled it initializes sessions and sets the key.
     * Cleans up the sessions when disabled.
     *
     * @param {boolean} enabled - whether E2EE should be enabled or not.
     * @returns {void}
     */
    async _setEnabled(enabled) {
        if (enabled) {
            await this._olmAdapter.initSessions();
        } else {
            this._olmAdapter.clearAllParticipantsSessions();
        }

        // Generate a random key in case we are enabling.
        this._key = enabled ? this._generateKey() : false;

        // Send it to others using the E2EE olm channel.
        const index = await this._olmAdapter.updateKey(this._key);

        // Set our key so we begin encrypting.
        this.e2eeCtx.setKey(this.conference.myUserId(), this._key, index);
    }

    /**
     * Handles an update in a participant's presence property.
     *
     * @param {JitsiParticipant} participant - The participant.
     * @param {string} name - The name of the property that changed.
     * @param {*} oldValue - The property's previous value.
     * @param {*} newValue - The property's new value.
     * @private
     */
    async _onParticipantPropertyChanged(participant, name, oldValue, newValue) {
        switch (name) {
        case 'e2ee.idKey':
            logger.debug(`Participant ${participant.getId()} updated their id key: ${newValue}`);
            break;
        case 'e2ee.enabled':
            if (!newValue && this.enabled) {
                this._olmAdapter.clearParticipantSession(participant);
            }
            break;
        }
    }

    /**
     * Advances (using ratcheting) the current key when a new participant joins the conference.
     * @private
     */
    _onParticipantJoined() {
        if (this._conferenceJoined && this.enabled) {
            this._ratchetKey();
        }
    }

    /**
     * Rotates the current key when a participant leaves the conference.
     * @private
     */
    _onParticipantLeft(id) {
        this.e2eeCtx.cleanup(id);

        if (this.enabled) {
            this._rotateKey();
        }
    }

    /**
     * Rotates the local key. Rotating the key implies creating a new one, then distributing it
     * to all participants and once they all received it, start using it.
     *
     * @private
     */
    async _rotateKeyImpl() {
        logger.debug('Rotating key');

        this._key = this._generateKey();
        const index = await this._olmAdapter.updateKey(this._key);

        this.e2eeCtx.setKey(this.conference.myUserId(), this._key, index);
    }

    /**
     * Advances the current key by using ratcheting.
     *
     * @private
     */
    async _ratchetKeyImpl() {
        logger.debug('Ratchetting key');

        const material = await importKey(this._key);
        const newKey = await ratchet(material);

        this._key = new Uint8Array(newKey);

        const index = this._olmAdapter.updateCurrentKey(this._key);

        this.e2eeCtx.setKey(this.conference.myUserId(), this._key, index);
    }

    /**
     * Handles an update in a participant's key.
     *
     * @param {string} id - The participant ID.
     * @param {Uint8Array | boolean} key - The new key for the participant.
     * @param {Number} index - The new key's index.
     * @private
     */
    _onParticipantKeyUpdated(id, key, index) {
        logger.debug(`Participant ${id} updated their key`);

        this.e2eeCtx.setKey(id, key, index);
    }

    /**
     * Generates a new 256 bit random key.
     *
     * @returns {Uint8Array}
     * @private
     */
    _generateKey() {
        return window.crypto.getRandomValues(new Uint8Array(32));
    }
}
