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

refactor(sdk-crypto): Room key sharing, introduce extensible strategy #3571

Closed
wants to merge 10 commits into from

Conversation

BillCarsonFr
Copy link
Member

@BillCarsonFr BillCarsonFr commented Jun 18, 2024

Fixes: #3562

Extracted the logic to collect the devices that should receive a room key.
Introduce a new CollectStrategy that will define how to sort devices that should receive the room_key and those that should receive a witheld code.
The common part of sharing (checking if the session should be rotated) is done by the CollectRecipientsHelper, then the actual selection is delegated depending on th CollectStrategy

There is no functional changes in this PR, a following PR will introduce an IdentityBased strategy that will select devices depending on if they are signed by their owner.

Also created some testing data that can be reused for various tests, will be used by the new strategy also. This is isolated in the first commit.

  • Public API changes documented in changelogs (optional)

Signed-off-by:

@BillCarsonFr BillCarsonFr force-pushed the valere/invisible_crypto/refactor_share branch from d584668 to 7f4d686 Compare June 18, 2024 12:38
@BillCarsonFr BillCarsonFr changed the title Valere/invisible crypto/refactor share refactor(sdk-crypto): Room key sharing, introduce extensible strategy Jun 18, 2024
@BillCarsonFr BillCarsonFr force-pushed the valere/invisible_crypto/refactor_share branch from 7f4d686 to 81a9f95 Compare June 18, 2024 12:39
@BillCarsonFr BillCarsonFr force-pushed the valere/invisible_crypto/refactor_share branch from 81a9f95 to 05ba146 Compare June 18, 2024 12:58
@BillCarsonFr BillCarsonFr marked this pull request as ready for review June 18, 2024 12:58
@BillCarsonFr BillCarsonFr requested a review from a team as a code owner June 18, 2024 12:58
@BillCarsonFr BillCarsonFr requested review from andybalaam and poljar and removed request for a team June 18, 2024 12:58
Copy link

codecov bot commented Jun 18, 2024

Codecov Report

Attention: Patch coverage is 82.19178% with 13 lines in your changes missing coverage. Please review.

Project coverage is 83.87%. Comparing base (b34a2cd) to head (ae0ff21).
Report is 62 commits behind head on main.

Files Patch % Lines
...dk-crypto/src/olm/group_sessions/share_strategy.rs 81.15% 13 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3571      +/-   ##
==========================================
+ Coverage   83.84%   83.87%   +0.03%     
==========================================
  Files         253      254       +1     
  Lines       25884    25903      +19     
==========================================
+ Hits        21702    21727      +25     
+ Misses       4182     4176       -6     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Contributor

@andybalaam andybalaam left a comment

Choose a reason for hiding this comment

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

Looks good, with a few questions and comments. I'd feel more comfortable if @poljar had time to look too.

let dan_device_that_will_get_the_key = &dan_devices_shared[0];
assert_eq!(dan_device_that_will_get_the_key.device_id().as_str(), "JHPUERYQUW");

// Check withthelds for others
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
// Check withthelds for others
// Check withhelds for others

let (_, code) = share_result
.withheld_devices
.iter()
.find(|(d, _)| d.device_id().as_str() == "FRGNMZVOKA")
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we use something like KeyDistributionTestData::dan_device() for this to avoid a magic constant?

/// conversation. A device is trusted if any of the following is true:
/// - It was manually marked as trusted.
/// - It was marked as verified via interactive verification.
/// - It is signed by it's owner identity, and this identity has
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
/// - It is signed by it's owner identity, and this identity has
/// - It is signed by its owner identity, and this identity has

pub withheld_devices: Vec<(ReadOnlyDevice, WithheldCode)>,
}

pub(crate) struct CollectRecipientsHelper {}
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there any need for this struct? Can't we just have collect_session_recipients as a free function?

Copy link
Contributor

Choose a reason for hiding this comment

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

Agree there.

// rotated for other reasons, we also need to check whether any
// of the devices in the session got deleted or blacklisted in the
// meantime. If so, we should also rotate the session.
if !should_rotate {
Copy link
Contributor

Choose a reason for hiding this comment

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

I would prefer if the contents of this if were a separate function - the containing function is very long.

Copy link
Contributor

Choose a reason for hiding this comment

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

I would probably prefer the body of the for loop to be a separate function, or even better the body inside the if let Some(shared) conditional to be put into a method on the OutboundGroupSession type.

}

async fn collect_session_recipients_device_based(
// &self,
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove commented code.

Copy link
Contributor

@poljar poljar left a comment

Choose a reason for hiding this comment

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

This PR is called a refactor but a non-trivial amount of behavioral changes has snuck in. Some of them seem to be bugs as well.


use crate::response_from_file;

/// ============================================================================
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think we have a convention of adding such a header, doesn't really add anything I would say.

It does look weird once rednered:

image

pub withheld_devices: Vec<(ReadOnlyDevice, WithheldCode)>,
}

pub(crate) struct CollectRecipientsHelper {}
Copy link
Contributor

Choose a reason for hiding this comment

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

Agree there.

}

impl CollectRecipientsHelper {
pub(crate) async fn collect_session_recipients(
Copy link
Contributor

Choose a reason for hiding this comment

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

It would have been much nicer if you first moved this function to this new file in a separate commit and only then started to modify it. It's a bit annoying to see what has changed and what was just moved.

For reference, I need to pick out the diff manually via:

git diff origin/main:crates/matrix-sdk-crypto/src/session_manager/group_sessions.rs crates/matrix-sdk-crypto/src/olm/group_sessions/share_strategy.rs

Even with this, the diff isn't smart enough to notice what was split out where.

Copy link
Member Author

Choose a reason for hiding this comment

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

Can you clarify? Should I drop everything and do a new PR and do that?

Copy link
Contributor

@poljar poljar Jun 25, 2024

Choose a reason for hiding this comment

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

No, please don't open a new PR, it took quite a bit effort to review it and please don't throw that work now away.

Can you clarify?

Let us introduce a simplified example, let's suppose that you have a file with this function:

// main.rs

fn greet() {
    println!("Hello, world!");
}

Now you need to refactor and slightly modify this function, you need add a parameter to it, you need to do the following changes:

  1. Delete the function from main.rs.
  2. Create a new file greetings.rs.
  3. Add the function to greetings.rs
  4. Modify the function to take a parameter
// greetings.rs

fn greet(name: &str) {
    println!("Hello, {}!", name);
}

If you do all 4 steps in a single commit the diff will look like:

--- main.rs
+++ main.rs
@@ -1,4 +1,0 @@
-// main.rs
-
-fn greet() {
-    println!("Hello, world!");
-}

--- /dev/null
+++ greetings.rs
@@ -0,0 +1,4 @@
+// greetings.rs
+
+fn greet(name: &str) {
+    println!("Hello, {}!", name);
+}

The modifications are a bit hard to spot, aren't they? Sure the function is 3 lines long, you can easily spot them if you pay attention and compare the green and red lines carefully.

Now if you instead commit step 1-3 first and commit step 4 separately you get two diffs which look like this:

--- main.rs
+++ main.rs
@@ -1,4 +1,0 @@
-// main.rs
-
-fn greet() {
-    println!("Hello, world!");
-}

--- /dev/null
+++ greetings.rs
@@ -0,0 +1,4 @@
+// greetings.rs
+
+fn greet() {
+    println!("Hello, world!");
+}

and:

--- greetings.rs
+++ greetings.rs
@@ -1,4 +1,4 @@
 // greetings.rs
 
-fn greet() {
-    println!("Hello, world!");
+fn greet(name: &str) {
+    println!("Hello, {}!", name);
 }

This makes the changes to the function stand out quite clearly, the first commit can be quickly checked if the move is done correctly, something Rust helps us with as well, and then we can focus our attention to the meaningful functional modifications.

let sharing_strategy_changed =
outbound.settings().sharing_strategy != settings.sharing_strategy;

let mut should_rotate =
Copy link
Contributor

Choose a reason for hiding this comment

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

This function seems to have received a downgrade in the documentation department, why was this comment removed:

// To protect the room history we need to rotate the session if either:
//
// 1. Any user left the room.
// 2. Any of the users' devices got deleted or blacklisted.
// 3. The history visibility changed.
// 4. The encryption algorithm changed.
//
// This is calculated in the following code and stored in this variable.
let mut should_rotate = user_left || visibility_changed || algorithm_changed;

let sharing_strategy_changed =
outbound.settings().sharing_strategy != settings.sharing_strategy;

let mut should_rotate =
Copy link
Contributor

Choose a reason for hiding this comment

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

This function seems to have received a downgrade in the documentation department, why was this comment removed:

// To protect the room history we need to rotate the session if either:
//
// 1. Any user left the room.
// 2. Any of the users' devices got deleted or blacklisted.
// 3. The history visibility changed.
// 4. The encryption algorithm changed.
//
// This is calculated in the following code and stored in this variable.
let mut should_rotate = user_left || visibility_changed || algorithm_changed;

// rotated for other reasons, we also need to check whether any
// of the devices in the session got deleted or blacklisted in the
// meantime. If so, we should also rotate the session.
if !should_rotate {
Copy link
Contributor

Choose a reason for hiding this comment

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

I would probably prefer the body of the for loop to be a separate function, or even better the body inside the if let Some(shared) conditional to be put into a method on the OutboundGroupSession type.

trace!(should_rotate, "Done calculating group session recipients");

Ok(CollectRecipientsResult { should_rotate, devices, withheld_devices })
CollectRecipientsHelper::collect_session_recipients(settings, &self.store, users, outbound)
Copy link
Contributor

Choose a reason for hiding this comment

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

This really feels like needless indirection. Perhaps it would make sense to make the collect_session_recipients() a constructor of CollectRecipientsResult if you don't like the free standing function suggestion.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks for the direction. I am not sure what's best but I think I will go with the free standing

})
}

async fn collect_session_recipients_device_based(
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it makes sense for this to be a free standing method or to be bound to the DeviceBasedStrategy enum variant, though you'll have to change the variant to be a tuple variant and add a struct for it, so perhaps a free standing function again makes more sense.

Furthermore, this could get a better name? It's not a different version of collect_session_recipients(), perhaps it could be called collect_recipient_devices().

}
});

if !recipients.is_empty() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why was this conditional added? Furthermore if we think that this is useful, why was it added only for the recipients set and not for the withheld_recipients set? Because of the allocation on the user id and the or_default()?

Copy link
Member Author

Choose a reason for hiding this comment

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

It was annoying in the tests to have these empty buckets of devices. Also was not usefull because we checked earlier for removed users so no need to maintain an empty bucket when looking for left devices.

pub withheld_devices: Vec<(ReadOnlyDevice, WithheldCode)>,
}

struct KeySharingResult {
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps a better name is RecipientDevices?

@BillCarsonFr
Copy link
Member Author

Will close and try to re-organize that PR into a new PR, that hopefully will match expectations

@BillCarsonFr
Copy link
Member Author

Closed in favor of #3605

@poljar poljar deleted the valere/invisible_crypto/refactor_share branch November 5, 2024 11:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants