-
-
Notifications
You must be signed in to change notification settings - Fork 624
RTCEncryptionManager: Joiner key rotation grace period #4911
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
base: develop
Are you sure you want to change the base?
Changes from all commits
1d798cb
0bda8e0
a23c629
46f3e89
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -153,18 +153,31 @@ export interface EncryptionConfig { | |||||
/** | ||||||
* The minimum time (in milliseconds) between each attempt to send encryption key(s). | ||||||
* e.g. if this is set to 1000, then we will send at most one key event every second. | ||||||
* @deprecated - Not used by the new encryption manager. | ||||||
*/ | ||||||
updateEncryptionKeyThrottle?: number; | ||||||
|
||||||
/** | ||||||
* Sometimes it is necessary to rotate the encryption key after a membership update. | ||||||
* For performance reasons we might not want to rotate the key immediately but allow future memberships to use the same key. | ||||||
* If 5 people join in a row in less than 5 seconds, we don't want to rotate the key for each of them. | ||||||
* If 5 people leave in a row in less than 5 seconds, we don't want to rotate the key for each of them. | ||||||
*/ | ||||||
keyRotationGracePeriodMs?: number; | ||||||
|
||||||
/** | ||||||
* The delay (in milliseconds) after a member leaves before we create and publish a new key, because people | ||||||
* tend to leave calls at the same time. | ||||||
* @deprecated - Not used by the new encryption manager. | ||||||
*/ | ||||||
makeKeyDelay?: number; | ||||||
/** | ||||||
* The delay (in milliseconds) between creating and sending a new key and starting to encrypt with it. This | ||||||
* gives other a chance to receive the new key to minimise the chance they don't get media they can't decrypt. | ||||||
* The total time between a member leaving and the call switching to new keys is therefore: | ||||||
* makeKeyDelay + useKeyDelay | ||||||
* The delay (in milliseconds) between sending a new key and starting to encrypt with it. This | ||||||
* gives others a chance to receive the new key to minimize the chance they get media they can't decrypt. | ||||||
* | ||||||
* The higher this value is, the better it is for existing members as they will have a smoother experience. | ||||||
* But it impacts new joiners: They will always have to wait `useKeyDelay` before being able to decrypt the media | ||||||
* (as it will be encrypted with the new key after the delay only), even if the key is already arrived before the delay. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
*/ | ||||||
useKeyDelay?: number; | ||||||
} | ||||||
|
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -60,12 +60,24 @@ export class RTCEncryptionManager implements IEncryptionManager { | |||||||||
* Ensures that there is only one distribute operation at a time for that call. | ||||||||||
*/ | ||||||||||
private currentKeyDistributionPromise: Promise<void> | null = null; | ||||||||||
|
||||||||||
/** | ||||||||||
* The time to wait before using the outbound session after it has been distributed. | ||||||||||
* This is to ensure that the key is delivered to all participants before it is used. | ||||||||||
* When creating the first key, this is set to 0, so that the key can be used immediately. | ||||||||||
*/ | ||||||||||
private delayRolloutTimeMillis = 1000; | ||||||||||
private delayRolloutTimeMillis = 5000; | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this the same as |
||||||||||
|
||||||||||
/** | ||||||||||
* We want to avoid rolling out a new outbound key when the previous one was created less than `skipRotationGracePeriod` milliseconds ago. | ||||||||||
* This is to avoid expensive key rotations when users quickly join the call in a row. | ||||||||||
* | ||||||||||
* This must be higher than `delayRolloutTimeMillis` to have an effect. | ||||||||||
* If it is lower, the current key will always be older than the grace period. | ||||||||||
* @private | ||||||||||
*/ | ||||||||||
private skipRotationGracePeriod = 10_000; | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here |
||||||||||
|
||||||||||
/** | ||||||||||
* If a new key distribution is being requested while one is going on, we will set this flag to true. | ||||||||||
* This will ensure that a new round is started after the current one. | ||||||||||
|
@@ -116,6 +128,7 @@ export class RTCEncryptionManager implements IEncryptionManager { | |||||||||
public join(joinConfig: EncryptionConfig | undefined): void { | ||||||||||
this.logger?.info(`Joining room`); | ||||||||||
this.delayRolloutTimeMillis = joinConfig?.useKeyDelay ?? 1000; | ||||||||||
this.skipRotationGracePeriod = joinConfig?.keyRotationGracePeriodMs ?? 10_000; | ||||||||||
Comment on lines
130
to
+131
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should totally be:
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or the other names from this class |
||||||||||
this.transport.on(KeyTransportEvents.ReceivedKeys, this.onNewKeyReceived); | ||||||||||
// Deprecate RoomKeyTransport: this can get removed. | ||||||||||
if (this.transport instanceof RoomAndToDeviceTransport) { | ||||||||||
|
@@ -280,26 +293,28 @@ export class RTCEncryptionManager implements IEncryptionManager { | |||||||||
let hasKeyChanged = false; | ||||||||||
if (anyLeft.length > 0) { | ||||||||||
// We need to rotate the key | ||||||||||
const newOutboundKey: OutboundEncryptionSession = { | ||||||||||
key: this.generateRandomKey(), | ||||||||||
creationTS: Date.now(), | ||||||||||
sharedWith: [], | ||||||||||
keyId: this.nextKeyIndex(), | ||||||||||
}; | ||||||||||
const newOutboundKey = this.createNewOutboundSession(); | ||||||||||
hasKeyChanged = true; | ||||||||||
|
||||||||||
this.logger?.info(`creating new outbound key index:${newOutboundKey.keyId}`); | ||||||||||
// Set this new key as the current one | ||||||||||
this.outboundSession = newOutboundKey; | ||||||||||
|
||||||||||
// Send | ||||||||||
toDistributeTo = toShareWith; | ||||||||||
outboundKey = newOutboundKey; | ||||||||||
} else if (anyJoined.length > 0) { | ||||||||||
// keep the same key | ||||||||||
// XXX In the future we want to distribute a ratcheted key not the current one | ||||||||||
toDistributeTo = anyJoined; | ||||||||||
outboundKey = this.outboundSession!; | ||||||||||
const now = Date.now(); | ||||||||||
const keyAge = now - this.outboundSession!.creationTS; | ||||||||||
// If the current key is recently created (less than `skipRotationGracePeriod`), we can keep it and just distribute it to the new joiners. | ||||||||||
if (keyAge < this.skipRotationGracePeriod) { | ||||||||||
// keep the same key | ||||||||||
// XXX In the future we want to distribute a ratcheted key, not the current one | ||||||||||
this.logger?.debug(`New joiners detected, but the key is recent enough (age:${keyAge}), keeping it`); | ||||||||||
toDistributeTo = anyJoined; | ||||||||||
outboundKey = this.outboundSession!; | ||||||||||
} else { | ||||||||||
// We need to rotate the key | ||||||||||
this.logger?.debug(`New joiners detected, rotating the key`); | ||||||||||
const newOutboundKey = this.createNewOutboundSession(); | ||||||||||
hasKeyChanged = true; | ||||||||||
toDistributeTo = toShareWith; | ||||||||||
outboundKey = newOutboundKey; | ||||||||||
} | ||||||||||
} else { | ||||||||||
// no changes | ||||||||||
return; | ||||||||||
|
@@ -330,6 +345,20 @@ export class RTCEncryptionManager implements IEncryptionManager { | |||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
private createNewOutboundSession(): OutboundEncryptionSession { | ||||||||||
const newOutboundKey: OutboundEncryptionSession = { | ||||||||||
key: this.generateRandomKey(), | ||||||||||
creationTS: Date.now(), | ||||||||||
sharedWith: [], | ||||||||||
keyId: this.nextKeyIndex(), | ||||||||||
}; | ||||||||||
|
||||||||||
this.logger?.info(`creating new outbound key index:${newOutboundKey.keyId}`); | ||||||||||
// Set this new key as the current one | ||||||||||
this.outboundSession = newOutboundKey; | ||||||||||
return newOutboundKey; | ||||||||||
} | ||||||||||
|
||||||||||
private nextKeyIndex(): number { | ||||||||||
if (this.outboundSession) { | ||||||||||
return (this.outboundSession!.keyId + 1) % 256; | ||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.