Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reverts #1032 and adds more verbose debug logging #1041

Merged
merged 2 commits into from
Feb 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .changeset/bright-islands-collect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
"livekit-client": patch
---
Fix transceiver reuse for e2ee and add more verbose e2ee debug logging
7 changes: 6 additions & 1 deletion src/e2ee/KeyProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { createKeyMaterialFromBuffer, createKeyMaterialFromString } from './util
export class BaseKeyProvider extends (EventEmitter as new () => TypedEventEmitter<KeyProviderCallbacks>) {
private keyInfoMap: Map<string, KeyInfo>;

private options: KeyProviderOptions;
private readonly options: KeyProviderOptions;

constructor(options: Partial<KeyProviderOptions> = {}) {
super();
Expand All @@ -29,6 +29,11 @@ export class BaseKeyProvider extends (EventEmitter as new () => TypedEventEmitte
*/
protected onSetEncryptionKey(key: CryptoKey, participantIdentity?: string, keyIndex?: number) {
const keyInfo: KeyInfo = { key, participantIdentity, keyIndex };
if (!this.options.sharedKey && !participantIdentity) {
throw new Error(
'participant identity needs to be passed for encryption key if sharedKey option is false',
);
}
this.keyInfoMap.set(`${participantIdentity ?? 'shared'}-${keyIndex ?? 0}`, keyInfo);
this.emit(KeyProviderEvent.SetKey, keyInfo);
}
Expand Down
26 changes: 26 additions & 0 deletions src/e2ee/worker/FrameCryptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@ export class FrameCryptor extends BaseFrameCryptor {
this.sifGuard = new SifGuard();
}

private get logContext() {
return {
identity: this.participantIdentity,
trackId: this.trackId,
fallbackCodec: this.videoCodec,
};
}

/**
* Assign a different participant to the cryptor.
* useful for transceiver re-use
Expand All @@ -96,6 +104,7 @@ export class FrameCryptor extends BaseFrameCryptor {
}

unsetParticipant() {
workerLogger.debug('unsetting participant', this.logContext);
this.participantIdentity = undefined;
}

Expand Down Expand Up @@ -143,6 +152,13 @@ export class FrameCryptor extends BaseFrameCryptor {
this.videoCodec = codec;
}

workerLogger.debug('Setting up frame cryptor transform', {
operation,
passedTrackId: trackId,
codec,
...this.logContext,
});

const transformFn = operation === 'encode' ? this.encodeFunction : this.decodeFunction;
const transformStream = new TransformStream({
transform: transformFn.bind(this),
Expand All @@ -159,6 +175,7 @@ export class FrameCryptor extends BaseFrameCryptor {
}

setSifTrailer(trailer: Uint8Array) {
workerLogger.debug('setting SIF trailer', { ...this.logContext, trailer });
this.sifTrailer = trailer;
}

Expand Down Expand Up @@ -212,6 +229,8 @@ export class FrameCryptor extends BaseFrameCryptor {
encodedFrame.timestamp,
);
let frameInfo = this.getUnencryptedBytes(encodedFrame);
workerLogger.debug('frameInfo for encoded frame', { ...frameInfo, ...this.logContext });

// Thіs is not encrypted and contains the VP8 payload descriptor or the Opus TOC byte.
const frameHeader = new Uint8Array(encodedFrame.data, 0, frameInfo.unencryptedBytes);

Expand Down Expand Up @@ -262,6 +281,7 @@ export class FrameCryptor extends BaseFrameCryptor {
workerLogger.error(e);
}
} else {
workerLogger.debug('failed to decrypt, emitting error', this.logContext);
this.emit(
CryptorEvent.Error,
new CryptorError(`encryption key missing for encoding`, CryptorErrorReason.MissingKey),
Expand All @@ -284,11 +304,13 @@ export class FrameCryptor extends BaseFrameCryptor {
// skip for decryption for empty dtx frames
encodedFrame.data.byteLength === 0
) {
workerLogger.debug('skipping empty frame', this.logContext);
this.sifGuard.recordUserFrame();
return controller.enqueue(encodedFrame);
}

if (isFrameServerInjected(encodedFrame.data, this.sifTrailer)) {
workerLogger.debug('enqueue SIF', this.logContext);
this.sifGuard.recordSif();

if (this.sifGuard.isSifAllowed()) {
Expand All @@ -312,6 +334,7 @@ export class FrameCryptor extends BaseFrameCryptor {
const decodedFrame = await this.decryptFrame(encodedFrame, keyIndex);
this.keys.decryptionSuccess();
if (decodedFrame) {
workerLogger.debug('enqueue decrypted frame', this.logContext);
return controller.enqueue(decodedFrame);
}
} catch (error) {
Expand Down Expand Up @@ -352,6 +375,8 @@ export class FrameCryptor extends BaseFrameCryptor {
throw new TypeError(`no encryption key found for decryption of ${this.participantIdentity}`);
}
let frameInfo = this.getUnencryptedBytes(encodedFrame);
workerLogger.debug('frameInfo for decoded frame', { ...frameInfo, ...this.logContext });

// Construct frame trailer. Similar to the frame header described in
// https://tools.ietf.org/html/draft-omara-sframe-00#section-4.2
// but we put it at the end.
Expand Down Expand Up @@ -566,6 +591,7 @@ export class FrameCryptor extends BaseFrameCryptor {
// @ts-expect-error payloadType is not yet part of the typescript definition and currently not supported in Safari
const payloadType = frame.getMetadata().payloadType;
const codec = payloadType ? this.rtpMap.get(payloadType) : undefined;
workerLogger.debug('reading codec from frame', { codec, ...this.logContext });
return codec;
}
}
Expand Down
14 changes: 9 additions & 5 deletions src/e2ee/worker/ParticipantKeyHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,15 +133,19 @@ export class ParticipantKeyHandler extends (EventEmitter as new () => TypedEvent

/**
* takes in a key material with `deriveBits` and `deriveKey` set as key usages
* and derives encryption keys from the material and sets it on the key ring buffer
* and derives encryption keys from the material and sets it on the key ring buffers
* together with the material
* also updates the currentKeyIndex
*/
async setKeyFromMaterial(material: CryptoKey, keyIndex = 0, emitRatchetEvent = false) {
const newIndex = keyIndex >= 0 ? keyIndex % this.cryptoKeyRing.length : -1;
workerLogger.debug(`setting new key with index ${newIndex}`);
async setKeyFromMaterial(material: CryptoKey, keyIndex: number, emitRatchetEvent = false) {
const keySet = await deriveKeys(material, this.keyProviderOptions.ratchetSalt);
this.setKeySet(keySet, newIndex >= 0 ? newIndex : this.currentKeyIndex, emitRatchetEvent);
const newIndex = keyIndex >= 0 ? keyIndex % this.cryptoKeyRing.length : this.currentKeyIndex;
workerLogger.debug(`setting new key with index ${keyIndex}`, {
usage: material.usages,
algorithm: material.algorithm,
ratchetSalt: this.keyProviderOptions.ratchetSalt,
});
this.setKeySet(keySet, newIndex, emitRatchetEvent);
if (newIndex >= 0) this.currentKeyIndex = newIndex;
}

Expand Down
33 changes: 16 additions & 17 deletions src/e2ee/worker/e2ee.worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ let isEncryptionEnabled: boolean = false;

let useSharedKey: boolean = false;

let sharedKey: CryptoKey | undefined;

let sifTrailer: Uint8Array | undefined;

let keyProviderOptions: KeyProviderOptions = KEY_PROVIDER_DEFAULTS;
Expand Down Expand Up @@ -72,10 +70,9 @@ onmessage = (ev) => {
break;
case 'setKey':
if (useSharedKey) {
workerLogger.warn('set shared key');
setSharedKey(data.key, data.keyIndex);
} else if (data.participantIdentity) {
workerLogger.warn(
workerLogger.info(
`set participant sender key ${data.participantIdentity} index ${data.keyIndex}`,
);
getParticipantKeyHandler(data.participantIdentity).setKey(data.key, data.keyIndex);
Expand Down Expand Up @@ -125,9 +122,7 @@ async function handleRatchetRequest(data: RatchetRequestMessage['data']) {
}

function getTrackCryptor(participantIdentity: string, trackId: string) {
let cryptor = participantCryptors.find(
(c) => c.getParticipantIdentity() === participantIdentity && c.getTrackId() === trackId,
);
let cryptor = participantCryptors.find((c) => c.getTrackId() === trackId);
if (!cryptor) {
workerLogger.info('creating new cryptor for', { participantIdentity });
if (!keyProviderOptions) {
Expand All @@ -146,8 +141,7 @@ function getTrackCryptor(participantIdentity: string, trackId: string) {
// assign new participant id to track cryptor and pass in correct key handler
cryptor.setParticipant(participantIdentity, getParticipantKeyHandler(participantIdentity));
}
if (sharedKey) {
}

return cryptor;
}

Expand All @@ -158,9 +152,6 @@ function getParticipantKeyHandler(participantIdentity: string) {
let keys = participantKeys.get(participantIdentity);
if (!keys) {
keys = new ParticipantKeyHandler(participantIdentity, keyProviderOptions);
if (sharedKey) {
keys.setKey(sharedKey);
}
keys.on(KeyHandlerEvent.KeyRatcheted, emitRatchetedKeys);
participantKeys.set(participantIdentity, keys);
}
Expand All @@ -169,24 +160,32 @@ function getParticipantKeyHandler(participantIdentity: string) {

function getSharedKeyHandler() {
if (!sharedKeyHandler) {
workerLogger.debug('creating new shared key handler');
sharedKeyHandler = new ParticipantKeyHandler('shared-key', keyProviderOptions);
}
return sharedKeyHandler;
}

function unsetCryptorParticipant(trackId: string, participantIdentity: string) {
participantCryptors
.find((c) => c.getParticipantIdentity() === participantIdentity && c.getTrackId() === trackId)
?.unsetParticipant();
const cryptor = participantCryptors.find(
(c) => c.getParticipantIdentity() === participantIdentity && c.getTrackId() === trackId,
);
if (!cryptor) {
workerLogger.warn('Could not unset participant on cryptor', { trackId, participantIdentity });
} else {
cryptor.unsetParticipant();
}
}

function setEncryptionEnabled(enable: boolean, participantIdentity: string) {
workerLogger.debug(`setting encryption enabled for all tracks of ${participantIdentity}`, {
enable,
});
encryptionEnabledMap.set(participantIdentity, enable);
}

function setSharedKey(key: CryptoKey, index?: number) {
workerLogger.debug('setting shared key');
sharedKey = key;
workerLogger.info('set shared key', { index });
getSharedKeyHandler().setKey(key, index);
}

Expand Down
15 changes: 11 additions & 4 deletions src/room/Room.ts
Original file line number Diff line number Diff line change
Expand Up @@ -574,16 +574,23 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
this.localParticipant.sid = pi.sid;
this.localParticipant.identity = pi.identity;

if (this.options.e2ee && this.e2eeManager) {
try {
this.e2eeManager.setSifTrailer(joinResponse.sifTrailer);
} catch (e: any) {
this.log.error(e instanceof Error ? e.message : 'Could not set SifTrailer', {
...this.logContext,
error: e,
});
}
}

// populate remote participants, these should not trigger new events
this.handleParticipantUpdates([pi, ...joinResponse.otherParticipants]);

if (joinResponse.room) {
this.handleRoomUpdate(joinResponse.room);
}

if (this.options.e2ee && this.e2eeManager) {
this.e2eeManager.setSifTrailer(joinResponse.sifTrailer);
}
};

private attemptConnection = async (
Expand Down
Loading