awslabs / mls-rs

An implementation of Messaging Layer Security (RFC 9420)
Apache License 2.0
104 stars 19 forks source link

[1.x] Externalize KeyPackageStorage and update join and write to storage generation API #209

Closed mulmarta closed 4 days ago

mulmarta commented 2 weeks ago

Background:

Part of #211

Before (0.x)

Join Group API

// Make a key package store that conforms to the KeyPackageStorage trait
let key_package_store = MyKeyPackageStore::new();

let client = Client::builder() 
        .key_package_repo(key_package_store) // Transfer the ownership of the key package repo to a client via the ClientBuilder
        ....
        .build();

// Join a group.
let (group, new_member_info) = client.join_group(tree_data, welcome_message).unwrap();

In the above, join_group internally finds the key package private key by calling KeyPackageStorage::get on (a clone of) the key_package_store with all key package references included in the welcome_message.

Write to Storage API

// Join a group
let (group, new_member_info) = client.join_group(tree_data, welcome_message).unwrap();

// Store state of the joined group.
group.write_to_storage().unwrap();

In the above, write_to_storage internally deletes the key package private key used to join by calling KeyPackageStorage::delete on (a clone of) the key_package_store owned by client.

After (1.x)

Join Group API

Client joins a group in three steps. First, it parses the Welcome message which returns information needed to fetch the private key from the storage. The same function will be used to parse other MLSMessage types like Commit, Proposal. Second, Client retrieves the private key and, third, it joins using the private key.

// Make a key package store that conforms to the KeyPackageStorage trait
let key_package_store = MyKeyPackageStore::new();

let client = Client::builder() 
        .... // No key package specific configuration 
        .build();

// Parse the Welcome message
let parsed_message = client.parse_message(welcome_message);

let ParsedMessage::Welcome {
    key_package_refs, // List of key package refs found in the message
    cipher_suite,
} = parsed_message
else {
    // Handle the case where this is not a Welcome message
};

// Independently retrieve key package private key
let (private_kp_data, key_package_ref) = key_package_store.get(key_package_refs, cipher_suite).unwrap();

// Join group
let (group, new_member_info) = client.join_group(tree_data, welcome_message, private_kp_data).unwrap();

// Delete used key package
key_package_store.delete(key_package_ref).unwrap();
mgeisler commented 2 weeks ago

Thanks for the write-up, Marta! This looks very nice to me.

I believe that this will overall make all method calls to Client and Group be "idempotent" (if that's the right word?)? That is, if I call it Client::join_group twice with the same arguments, I get the same (group, new_member_info) values back?

(Module any randomness that might be picked when the values are created, but semantically the values would compare equal after two calls.)

mulmarta commented 1 week ago

You're right that join_group will be idempotent. I'm not 100% sure about Group functions. E.g. if you call Group::process_incoming_message with a ciphertext, I think for forward secrecy, Group should delete the decryption key from memory? Of course if you create Group again with the same snapshot, then you can receive the ciphertext for the second time.

mgeisler commented 1 week ago

E.g. if you call Group::process_incoming_message with a ciphertext, I think for forward secrecy, Group should delete the decryption key from memory?

Yeah, it makes sense that there is an in-memory state which can change!

tomleavy commented 1 week ago

Yes, the good news is we already have this in place in order to support processing multiple messages before the user calls write to storage.

tomleavy commented 1 week ago

@mulmarta was working on the write_to_storage story, and I feel like we can avoid key package interaction in there all together by just having a property of private_kp_data in the above example containing the KeyPackageRef that needs to be deleted?

tomleavy commented 4 days ago

Closing in favor of #215