Skip to content

Commit

Permalink
feat: add getter for external sender to seed subconversations
Browse files Browse the repository at this point in the history
  • Loading branch information
beltram committed Feb 16, 2024
1 parent e8998cf commit 2b423b1
Show file tree
Hide file tree
Showing 13 changed files with 150 additions and 9 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -1905,7 +1905,7 @@ the FFI. Still uncertain about the root cause but to move on all the parameters
* `pkb`: the CBOR-serialized proteus PreKeyBundle
* **[BREAKING]** Added an API to mark subconversations as child of another one (`mark_conversation_as_child_of`)
* This is breaking because this now allows us to provide the parent conversation's client list in the `client_is_existing_group_user` callback, which adds a new parameter to it
* **[BREAKING]** `wipe_conversation` is now automatically called when a commit removing the local client is recieved.
* **[BREAKING]** `wipe_conversation` is now automatically called when a commit removing the local client is received.
* **[BREAKING]** Huge internal change on how we cache MLS groups and Proteus sessions in memory
* This affects some APIs that became async on the TS bindings
* Our previous `HashMap`-based cache could grow indefinitely in the case of massive accounts with many, many groups/conversations, each containing a ton of clients. This replaces this memory store by a LRU cache having the following properties:
Expand Down
2 changes: 1 addition & 1 deletion CHANGELOG.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -651,7 +651,7 @@ the FFI. Still uncertain about the root cause but to move on all the parameters
* `pkb`: the CBOR-serialized proteus PreKeyBundle
* **[BREAKING]** Added an API to mark subconversations as child of another one (`mark_conversation_as_child_of`)
* This is breaking because this now allows us to provide the parent conversation's client list in the `client_is_existing_group_user` callback, which adds a new parameter to it
* **[BREAKING]** `wipe_conversation` is now automatically called when a commit removing the local client is recieved.
* **[BREAKING]** `wipe_conversation` is now automatically called when a commit removing the local client is received.
* **[BREAKING]** Huge internal change on how we cache MLS groups and Proteus sessions in memory
* This affects some APIs that became async on the TS bindings
* Our previous `HashMap`-based cache could grow indefinitely in the case of massive accounts with many, many groups/conversations, each containing a ton of clients. This replaces this memory store by a LRU cache having the following properties:
Expand Down
12 changes: 12 additions & 0 deletions crypto-ffi/bindings/js/CoreCrypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1646,6 +1646,18 @@ export class CoreCrypto {
);
}

/**
* Returns the raw public key of the single external sender present in this group.
* This should be used to initialize a subconversation
*
* @param conversationId - The group's ID
*
* @returns A `Uint8Array` representing the external sender raw public key
*/
async getExternalSender(conversationId: ConversationId): Promise<Uint8Array> {
return await CoreCryptoError.asyncMapErr(this.#cc.get_external_sender(conversationId));
}

/**
* Returns all clients from group's members
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,17 @@ class MLSClient(private val cc: com.wire.crypto.CoreCrypto) {
return cc.exportSecretKey(id.lower(), keyLength).toAvsSecret()
}

/**
* Returns the raw public key of the single external sender present in this group.
* This should be used to initialize a subconversation
*
* @param id conversation identifier
* @param keyLength the length of the key to be derived. If the value is higher than the bounds of `u16` or the context hash * 255, an error will be returned
*/
suspend fun getExternalSender(id: MLSGroupId): ExternalSenderKey {
return cc.getExternalSender(id.lower()).toExternalSenderKey()
}

/**
* Indicates when to mark a conversation as not verified i.e. when not all its members have a X509.
* Credential generated by Wire's end-to-end identity enrollment
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ value class ExternalSenderKey(override val value: ByteArray) : Uniffi {
override fun toString() = value.toHex()
}

fun ByteArray.toExternalSenderKey() = ExternalSenderKey(this)

@JvmInline
value class Welcome(override val value: ByteArray) : Uniffi {
override fun toString() = value.toHex()
Expand Down
9 changes: 9 additions & 0 deletions crypto-ffi/bindings/swift/Sources/CoreCrypto/CoreCrypto.swift
Original file line number Diff line number Diff line change
Expand Up @@ -964,6 +964,15 @@ public class CoreCryptoWrapper {
try await self.coreCrypto.exportSecretKey(conversationId: conversationId, keyLength: keyLength)
}

/// Returns the raw public key of the single external sender present in this group.
/// This should be used to initialize a subconversation
///
/// - parameter conversationId: conversation identifier
/// - returns a byte array representing the external sender raw public key
public func getExternalSender(conversationId: ConversationId) async throws -> [UInt8] {
try await self.coreCrypto.getExternalSender(conversationId: conversationId)
}

/// Returns all clients from group's members
///
/// - parameter conversationId: conversation identifier
Expand Down
5 changes: 5 additions & 0 deletions crypto-ffi/src/generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1262,6 +1262,11 @@ impl CoreCrypto {
.export_secret_key(&conversation_id, key_length as usize)
.await?)
}

/// See [core_crypto::mls::MlsCentral::get_external_sender]
pub async fn get_external_sender(&self, conversation_id: Vec<u8>) -> CoreCryptoResult<Vec<u8>> {
Ok(self.central.lock().await.get_external_sender(&conversation_id).await?)
}
}

#[derive(Debug, Copy, Clone, uniffi::Enum)]
Expand Down
19 changes: 19 additions & 0 deletions crypto-ffi/src/wasm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2357,6 +2357,25 @@ impl CoreCrypto {
)
}

/// Returns: [`WasmCryptoResult<Vec<u8>>`]
///
/// see [core_crypto::mls::MlsCentral::get_external_sender]
pub fn get_external_sender(&self, id: ConversationId) -> Promise {
let this = self.inner.clone();
future_to_promise(
async move {
let ext_sender = this
.write()
.await
.get_external_sender(&id.to_vec())
.await
.map_err(CoreCryptoError::from)?;
WasmCryptoResult::Ok(Uint8Array::from(ext_sender.as_slice()).into())
}
.err_into(),
)
}

/// Returns: [`WasmCryptoResult<Box<[js_sys::Uint8Array]>`]
///
/// see [core_crypto::mls::MlsCentral::get_client_ids]
Expand Down
11 changes: 7 additions & 4 deletions crypto/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,12 +237,15 @@ pub enum CryptoError {
/// Json error
#[error(transparent)]
JsonError(#[from] serde_json::Error),
/// The recieved commit is deemed stale and is from an older epoch
#[error("The recieved commit is deemed stale and is from an older epoch.")]
/// The received commit is deemed stale and is from an older epoch
#[error("The received commit is deemed stale and is from an older epoch.")]
StaleCommit,
/// The recieved proposal is deemed stale and is from an older epoch
#[error("The recieved proposal is deemed stale and is from an older epoch.")]
/// The received proposal is deemed stale and is from an older epoch
#[error("The received proposal is deemed stale and is from an older epoch.")]
StaleProposal,
/// The group lacks an ExternalSender extension whereas it should have at least one
#[error("The group lacks an ExternalSender extension whereas it should have at least one")]
MissingExternalSenderExtension,
}

impl From<MlsError> for CryptoError {
Expand Down
65 changes: 65 additions & 0 deletions crypto/src/mls/conversation/external_sender.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use crate::{
prelude::{ConversationId, MlsCentral, MlsConversation},
prelude::{CryptoError, CryptoResult},
};

impl MlsCentral {
/// Returns the raw public key of the single external sender present in this group.
/// This should be used to initialize a subconversation
pub async fn get_external_sender(&mut self, id: &ConversationId) -> CryptoResult<Vec<u8>> {
self.get_conversation(id)
.await?
.read()
.await
.get_external_sender()
.await
}
}

impl MlsConversation {
async fn get_external_sender(&self) -> CryptoResult<Vec<u8>> {
let ext_senders = self
.group
.group_context_extensions()
.external_senders()
.ok_or(CryptoError::MissingExternalSenderExtension)?;
let ext_sender = ext_senders.first().ok_or(CryptoError::MissingExternalSenderExtension)?;
let ext_sender_public_key = ext_sender.signature_key().as_slice().to_vec();
Ok(ext_sender_public_key)
}
}

#[cfg(test)]
pub mod tests {
use wasm_bindgen_test::*;

use crate::test_utils::*;

wasm_bindgen_test_configure!(run_in_browser);

#[apply(all_cred_cipher)]
#[wasm_bindgen_test]
pub async fn should_fetch_ext_sender(case: TestCase) {
run_test_with_client_ids(case.clone(), ["alice"], move |[mut alice_central]| {
Box::pin(async move {
let id = conversation_id();

// by default in test no external sender is set. Let's add one
let mut cfg = case.cfg.clone();
let external_sender = alice_central.mls_central.rand_external_sender(&case);
cfg.external_senders = vec![external_sender.clone()];

alice_central
.mls_central
.new_conversation(&id, case.credential_type, cfg)
.await
.unwrap();

let alice_ext_sender = alice_central.mls_central.get_external_sender(&id).await.unwrap();
assert!(!alice_ext_sender.is_empty());
assert_eq!(alice_ext_sender, external_sender.signature_key().as_slice().to_vec());
})
})
.await
}
}
1 change: 1 addition & 0 deletions crypto/src/mls/conversation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ mod duplicate;
mod durability;
pub mod encrypt;
pub mod export;
pub(crate) mod external_sender;
pub(crate) mod group_info;
mod leaf_node_validation;
pub mod merge;
Expand Down
18 changes: 16 additions & 2 deletions crypto/src/test_utils/central.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@
// along with this program. If not, see http://www.gnu.org/licenses/.

use openmls::prelude::{
group_info::VerifiableGroupInfo, Credential, CredentialWithKey, CryptoConfig, HpkePublicKey, KeyPackage,
KeyPackageIn, LeafNodeIndex, Lifetime, MlsMessageIn, QueuedProposal, SignaturePublicKey, StagedCommit,
group_info::VerifiableGroupInfo, Credential, CredentialWithKey, CryptoConfig, ExternalSender, HpkePublicKey,
KeyPackage, KeyPackageIn, LeafNodeIndex, Lifetime, MlsMessageIn, QueuedProposal, SignaturePublicKey, StagedCommit,
};
use openmls_traits::crypto::OpenMlsCrypto;
use openmls_traits::{types::SignatureScheme, OpenMlsCryptoProvider};
use tls_codec::{Deserialize, Serialize};
use wire_e2e_identity::prelude::WireIdentityReader;
Expand Down Expand Up @@ -553,6 +554,19 @@ impl MlsCentral {
pub async fn members_count(&mut self, id: &ConversationId) -> u32 {
self.get_conversation_unchecked(id).await.members().len() as u32
}

pub fn rand_external_sender(&self, case: &TestCase) -> ExternalSender {
let sc = case.signature_scheme();

let crypto = self.mls_backend.crypto();
let (_, pk) = crypto.signature_key_gen(sc).unwrap();

let signature_key = SignaturePublicKey::from(pk);

let credential = Credential::new_basic(b"server".to_vec());

ExternalSender::new(signature_key, credential)
}
}

impl MlsConversation {
Expand Down
2 changes: 1 addition & 1 deletion interop/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ async fn run_mls_test(chrome_driver_addr: &std::net::SocketAddr) -> Result<()> {
.decrypt_message(&conversation_id, message_to_decrypt)
.await?
.app_msg
.ok_or_else(|| eyre!("[MLS] No message recieved on master client"))?;
.ok_or_else(|| eyre!("[MLS] No message received on master client"))?;

let decrypted_master = String::from_utf8(decrypted_master_raw)?;

Expand Down

0 comments on commit 2b423b1

Please sign in to comment.