Skip to content

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

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from

Conversation

BillCarsonFr
Copy link
Member

@BillCarsonFr BillCarsonFr commented Jul 8, 2025

Fixes https://github.com/element-hq/voip-internal/issues/371

Checklist

  • Tests written for new code (and old code if feasible).
  • New or updated public/exported symbols have accurate TSDoc documentation.
  • Linter and other CI checks pass.
  • Sign-off given on the changes (see CONTRIBUTING.md).

@BillCarsonFr BillCarsonFr force-pushed the valere/rtc/encryption_mgr/rotate_joiner_with_grace branch from cd3bde8 to 1e5efc3 Compare July 8, 2025 14:52
@BillCarsonFr BillCarsonFr added the T-Task Tasks for the team like planning label Jul 8, 2025
@BillCarsonFr BillCarsonFr marked this pull request as ready for review July 8, 2025 15:22
@BillCarsonFr BillCarsonFr requested a review from a team as a code owner July 8, 2025 15:22
@BillCarsonFr BillCarsonFr requested review from hughns and toger5 July 8, 2025 15:22
Copy link
Contributor

@toger5 toger5 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation looks very simple (In terms of: done in a way that is easy to follow along) and sane. I do have questions on the tests. So I need a bit of help (with comments) to understand what is supposed to happen.

Comment on lines 179 to 181
const gracePeriod = 15_000; // 15 seconds
// initial rollout
encryptionManager.join(undefined);
encryptionManager.join({ keyRotationGracePeriodMs: gracePeriod });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this we can just move into the join() method?
Since we use the object config the key is written out and the reason for the value is explained

Comment on lines 179 to 181
const gracePeriod = 15_000; // 15 seconds
// initial rollout
encryptionManager.join(undefined);
encryptionManager.join({ keyRotationGracePeriodMs: gracePeriod });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const gracePeriod = 15_000; // 15 seconds
// initial rollout
encryptionManager.join(undefined);
encryptionManager.join({ keyRotationGracePeriodMs: gracePeriod });
encryptionManager.join({ keyRotationGracePeriodMs: 15_000 });

Comment on lines 231 to 237
const gracePeriod = 3_000; // 15 seconds
const useKeyDelay = 5_000; // 5 seconds
// initial rollout
encryptionManager.join({
useKeyDelay,
keyRotationGracePeriodMs: gracePeriod,
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const gracePeriod = 3_000; // 15 seconds
const useKeyDelay = 5_000; // 5 seconds
// initial rollout
encryptionManager.join({
useKeyDelay,
keyRotationGracePeriodMs: gracePeriod,
});
// initial rollout
encryptionManager.join({
useKeyDelay: 3_000, // 3 seconds
keyRotationGracePeriodMs: 5_000, // 5 seconds
});

The comment seemed wrong (3 vs 15 s)


// A new member joins, that should trigger a key rotation.
members.push(aCallMembership("@carl:example.org", "CARLDEVICE"));
encryptionManager.onMembershipsUpdate([]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can we just pass [] as the old memberships here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am thinking of sth like:

const oldMembers = [...members];
members.push(aCallMembership("@carl:example.org", "CARLDEVICE"));
encryptionManager.onMembershipsUpdate(oldMembers);

Just to make sure the test env is more correct to the real world. Even if the curren impl also works with []
(also makes the test easier to read if we dont know exactly what the onMembershipsUpdate param means)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might as well deprecate it? as it is not used

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah in that case yes!

// A new member joins, within the grace period, but under the delay period
members.push(aCallMembership("@david:example.org", "DAVDEVICE"));
await jest.advanceTimersByTimeAsync(3_000);
encryptionManager.onMembershipsUpdate([]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above.
Shouldn't the old memberships be the correct prev memberships, so the test more properly represents a real world scenario?

* 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* If 5 people leave 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.
* So we do share the key which was already used live for <5s to new joiners. This does result in a potential leak up to the configured time of call media.
* This has to be considered when choosing a value for this property.

*
* 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* (as it will be encrypted with the new key after the delay only), even if the key is already arrived before the delay.
* (as it will be encrypted with the new key after the delay only), even if the key has already arrived before the delay.

/**
* 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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this the same as useKeyDelay. If so we should rename one of them.

* If it is lower, the current key will always be older than the grace period.
* @private
*/
private skipRotationGracePeriod = 10_000;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here keyRotationGracePeriodMs

Comment on lines 116 to +117
this.delayRolloutTimeMillis = joinConfig?.useKeyDelay ?? 1000;
this.skipRotationGracePeriod = joinConfig?.keyRotationGracePeriodMs ?? 10_000;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should totally be:

Suggested change
this.delayRolloutTimeMillis = joinConfig?.useKeyDelay ?? 1000;
this.skipRotationGracePeriod = joinConfig?.keyRotationGracePeriodMs ?? 10_000;
this.useKeyDelay = joinConfig?.useKeyDelay ?? 1000;
this.keyRotationGracePeriodMs = joinConfig?.keyRotationGracePeriodMs ?? 10_000;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or the other names from this class

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-Task Tasks for the team like planning
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants