diff --git a/crypto-ffi/bindings/js/CoreCrypto.ts b/crypto-ffi/bindings/js/CoreCrypto.ts index 1a5ede757f..250c5f9801 100644 --- a/crypto-ffi/bindings/js/CoreCrypto.ts +++ b/crypto-ffi/bindings/js/CoreCrypto.ts @@ -187,25 +187,6 @@ export interface ConversationConfiguration { * Implementation specific configuration */ custom?: CustomConfiguration; - /** - * Trust anchors to be added in the group's context extensions - */ - perDomainTrustAnchors?: PerDomainTrustAnchor[]; -} - -/** - * A wrapper containing the configuration for trust anchors to be added in the group's context - * extensions - */ -export interface PerDomainTrustAnchor { - /** - * Domain name of the owning backend this anchor refers to. One of the certificate in the chain has to have this domain in its SANs - */ - domain_name: string; - /** - * PEM encoded (partial) certificate chain. This contains the certificate chain for the CA certificate issuing the E2E Identity certificates - */ - intermediate_certificate_chain: string; } /** @@ -1017,14 +998,12 @@ export class CoreCrypto { ciphersuite, externalSenders, custom = {}, - perDomainTrustAnchors = [], } = configuration || {}; const config = new ConversationConfigurationFfi( ciphersuite, externalSenders, custom?.keyRotationSpan, custom?.wirePolicy, - perDomainTrustAnchors as any[] ); const ret = await CoreCryptoError.asyncMapErr( this.#cc.create_conversation( @@ -1117,54 +1096,6 @@ export class CoreCrypto { ); } - /** - * Updates the trust anchors for a conversation. This should be called when a federated event happens (new team added/removed). - * Clients should add and/or remove trust anchors from the new backend to the conversation. The method will check - * for duplicated domains and the validity of the certificate chain. - * - * **CAUTION**: {@link CoreCrypto.commitAccepted} **HAS TO** be called afterwards **ONLY IF** the Delivery Service responds - * '200 OK' to the {@link CommitBundle} upload. It will "merge" the commit locally i.e. increment the local group - * epoch, use new encryption secrets etc... - * - * @param conversationId - The ID of the conversation - * @param removeDomainNames - Domains to remove from the trust anchors - * @param addTrustAnchors - New trust anchors to add to the conversation - * - * @returns A {@link CommitBundle} - */ - async updateTrustAnchorsFromConversation( - conversationId: ConversationId, - removeDomainNames: string[], - addTrustAnchors: PerDomainTrustAnchor[] - ): Promise { - try { - const ffiRet: CoreCryptoFfiTypes.CommitBundle = - await CoreCryptoError.asyncMapErr( - this.#cc.update_trust_anchors_from_conversation( - conversationId, - removeDomainNames, - addTrustAnchors - ) - ); - - const gi = ffiRet.group_info; - - const ret: CommitBundle = { - welcome: ffiRet.welcome, - commit: ffiRet.commit, - groupInfo: { - encryptionType: gi.encryption_type, - ratchetTreeType: gi.ratchet_tree_type, - payload: gi.payload, - }, - }; - - return ret; - } catch (e) { - throw CoreCryptoError.fromStdError(e as Error); - } - } - /** * Ingest a TLS-serialized MLS welcome message to join an existing MLS group * diff --git a/crypto-ffi/bindings/jvm/src/main/kotlin/com/wire/crypto/client/MLSClient.kt b/crypto-ffi/bindings/jvm/src/main/kotlin/com/wire/crypto/client/MLSClient.kt index 590aa90615..b89732c3f1 100644 --- a/crypto-ffi/bindings/jvm/src/main/kotlin/com/wire/crypto/client/MLSClient.kt +++ b/crypto-ffi/bindings/jvm/src/main/kotlin/com/wire/crypto/client/MLSClient.kt @@ -206,20 +206,17 @@ class MLSClient(private val cc: com.wire.crypto.CoreCrypto) { * @param ciphersuite of the conversation. A credential for the given ciphersuite must already have been created * @param creatorCredentialType kind of credential the creator wants to create the group with * @param externalSenders keys fetched from backend for validating external remove proposals - * @param perDomainTrustAnchors 🚧 in progress. Leave empty for now */ suspend fun createConversation( id: MLSGroupId, ciphersuite: Ciphersuite = Ciphersuite.MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519, creatorCredentialType: CredentialType = CredentialType.Basic, externalSenders: List = emptyList(), - perDomainTrustAnchors: List = emptyList(), ) { val cfg = com.wire.crypto.ConversationConfiguration( ciphersuite.lower(), externalSenders.map { it.lower() }, defaultGroupConfiguration, - perDomainTrustAnchors ) cc.createConversation(id.lower(), creatorCredentialType.lower(), cfg) @@ -259,23 +256,6 @@ class MLSClient(private val cc: com.wire.crypto.CoreCrypto) { return cc.encryptMessage(id.lower(), message.lower()).toMlsMessage() } - /** - * Creates a Commit with a GroupContextExtension Proposal for updating the TrustAnchors of the group - * - * 🚧 Work in progress - * - * @param id conversation identifier - * @param removeDomainNames domains to remove, can intersect [addTrustAnchors]] - * @param addTrustAnchors new Anchors to add - */ - suspend fun updateTrustAnchorsFromConversation( - id: MLSGroupId, - removeDomainNames: List, - addTrustAnchors: List, - ): CommitBundle { - return cc.updateTrustAnchorsFromConversation(id.lower(), removeDomainNames, addTrustAnchors).lift() - } - /** * Decrypts a message for a given conversation * diff --git a/crypto-ffi/bindings/swift/Sources/CoreCrypto/CoreCrypto.swift b/crypto-ffi/bindings/swift/Sources/CoreCrypto/CoreCrypto.swift index 07607847d6..4cebec3709 100644 --- a/crypto-ffi/bindings/swift/Sources/CoreCrypto/CoreCrypto.swift +++ b/crypto-ffi/bindings/swift/Sources/CoreCrypto/CoreCrypto.swift @@ -204,7 +204,7 @@ public struct ProteusAutoPrekeyBundle: ConvertToInner { public struct ConversationConfiguration: ConvertToInner { typealias Inner = CoreCryptoSwift.ConversationConfiguration func convert() -> Inner { - return CoreCryptoSwift.ConversationConfiguration(ciphersuite: self.ciphersuite, externalSenders: self.externalSenders, custom: self.custom.convert(), self.perDomainTrustAnchor.convert()) + return CoreCryptoSwift.ConversationConfiguration(ciphersuite: self.ciphersuite, externalSenders: self.externalSenders, custom: self.custom.convert()) } /// Conversation ciphersuite @@ -213,32 +213,11 @@ public struct ConversationConfiguration: ConvertToInner { public var externalSenders: [[UInt8]] /// Implementation specific configuration public var custom: CustomConfiguration - /// Trust anchors to be added in the group's context extensions - public var perDomainTrustAnchor: PerDomainTrustAnchor - public init(ciphersuite: UInt16, externalSenders: [[UInt8]], custom: CustomConfiguration, perDomainTrustAnchor: PerDomainTrustAnchor) { + public init(ciphersuite: UInt16, externalSenders: [[UInt8]], custom: CustomConfiguration) { self.ciphersuite = ciphersuite self.externalSenders = externalSenders self.custom = custom - self.perDomainTrustAnchor = perDomainTrustAnchor - } -} - -/// A wrapper containing the configuration for trust anchors to be added in the group's context extensions -public struct PerDomainTrustAnchor: ConvertToInner { - typealias Inner = CoreCryptoSwift.PerDomainTrustAnchor - func convert() -> Inner { - return CoreCryptoSwift.PerDomainTrustAnchor(domain_name: self.domainName, intermediate_certificate_chain: self.intermediateCertificateChain) - } - - /// Domain name in which the trust anchor belongs to - public var domainName: String - /// PEM encoded certificate chain - public var intermediateCertificateChain: String - - public init(domainName: String, intermediateCertificateChain: String) { - self.domainName = domainName - self.intermediateCertificateChain = intermediateCertificateChain } } @@ -849,19 +828,6 @@ public class CoreCryptoWrapper { return try await self.coreCrypto.encryptMessage(conversationId: conversationId, message: message) } - /// Updates the trust anchors for a conversation - /// - /// - parameter conversationId - The ID of the conversation - /// - parameter removeDomainNames - Domains to remove from the trust anchors - /// - parameter addTrustAnchors - New trust anchors to add to the conversation - /// - /// - returns: A ``CommitBundle`` byte array to fan out to the Delivery Service - public func updateTrustAnchorsFromConversation(conversationId: ConversationId, removeDomainNames: [string], addTrustAnchors: [PerDomainTrustAnchor]) async throws -> CommitBundle { - return try await self.coreCrypto.update_trust_anchors_from_conversation(conversationId: conversationId, removeDomainNames: removeDomainNames, addTrustAnchors: addTrustAnchors.map({ (anchor) -> CoreCryptoSwift.PerDomainTrustAnchor in - return anchor.convert() - })).convertTo() - } - /// Creates a new add proposal within a group /// /// - parameter conversationId: conversation identifier diff --git a/crypto-ffi/src/generic.rs b/crypto-ffi/src/generic.rs index 085ffd2fc7..33f0b38188 100644 --- a/crypto-ffi/src/generic.rs +++ b/crypto-ffi/src/generic.rs @@ -503,14 +503,6 @@ pub struct ConversationConfiguration { pub ciphersuite: Ciphersuite, pub external_senders: Vec>, pub custom: CustomConfiguration, - pub per_domain_trust_anchors: Vec, -} - -#[derive(Debug, Clone, uniffi::Record)] -/// See [core_crypto::prelude::PerDomainTrustAnchor] -pub struct PerDomainTrustAnchor { - pub domain_name: String, - pub intermediate_certificate_chain: String, } impl TryInto for ConversationConfiguration { @@ -521,24 +513,11 @@ impl TryInto for ConversationConfiguration { ciphersuite: self.ciphersuite.into(), ..Default::default() }; - cfg.set_raw_external_senders(self.external_senders); - - cfg.per_domain_trust_anchors = self.per_domain_trust_anchors.into_iter().map(|a| a.into()).collect(); - Ok(cfg) } } -impl From for core_crypto::prelude::PerDomainTrustAnchor { - fn from(cfg: PerDomainTrustAnchor) -> Self { - Self { - domain_name: cfg.domain_name, - intermediate_certificate_chain: cfg.intermediate_certificate_chain, - } - } -} - #[derive(Debug, Copy, Clone, Default, Eq, PartialEq, uniffi::Enum)] #[repr(u8)] pub enum MlsWirePolicy { @@ -1030,25 +1009,6 @@ impl CoreCrypto { .await?) } - /// See [core_crypto::mls::MlsCentral::update_trust_anchors_from_conversation] - pub async fn update_trust_anchors_from_conversation( - &self, - id: ConversationId, - remove_domain_names: Vec, - add_trust_anchors: Vec, - ) -> CoreCryptoResult { - self.central - .lock() - .await - .update_trust_anchors_from_conversation( - &id, - remove_domain_names, - add_trust_anchors.into_iter().map(|a| a.into()).collect(), - ) - .await? - .try_into() - } - /// See [core_crypto::mls::MlsCentral::conversation_exists] pub async fn conversation_exists(&self, conversation_id: Vec) -> bool { self.central.lock().await.conversation_exists(&conversation_id).await diff --git a/crypto-ffi/src/wasm.rs b/crypto-ffi/src/wasm.rs index b02c6471b4..e4363b88c3 100644 --- a/crypto-ffi/src/wasm.rs +++ b/crypto-ffi/src/wasm.rs @@ -784,25 +784,6 @@ pub struct ConversationConfiguration { ciphersuite: Option, external_senders: Vec>, custom: CustomConfiguration, - per_domain_trust_anchors: Vec, -} - -#[wasm_bindgen] -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct PerDomainTrustAnchor { - domain_name: String, - intermediate_certificate_chain: String, -} - -#[wasm_bindgen] -impl PerDomainTrustAnchor { - #[wasm_bindgen(constructor)] - pub fn new(domain_name: String, intermediate_certificate_chain: String) -> Self { - Self { - domain_name, - intermediate_certificate_chain, - } - } } #[wasm_bindgen] @@ -813,7 +794,6 @@ impl ConversationConfiguration { external_senders: Option>, key_rotation_span: Option, wire_policy: Option, - per_domain_trust_anchors: js_sys::Array, ) -> WasmCryptoResult { let external_senders = external_senders .map(|exs| exs.iter().cloned().map(|jsv| jsv.to_vec()).collect()) @@ -822,27 +802,10 @@ impl ConversationConfiguration { ciphersuite, external_senders, custom: CustomConfiguration::new(key_rotation_span, wire_policy), - per_domain_trust_anchors: if per_domain_trust_anchors.is_null() { - vec![] - } else { - per_domain_trust_anchors - .into_iter() - .map(|js_anchor| Ok(serde_wasm_bindgen::from_value(js_anchor)?)) - .collect::>>()? - }, }) } } -impl From for core_crypto::prelude::PerDomainTrustAnchor { - fn from(wasm_cfg: PerDomainTrustAnchor) -> Self { - Self { - domain_name: wasm_cfg.domain_name, - intermediate_certificate_chain: wasm_cfg.intermediate_certificate_chain, - } - } -} - impl TryInto for ConversationConfiguration { type Error = CoreCryptoError; fn try_into(mut self) -> WasmCryptoResult { @@ -858,8 +821,6 @@ impl TryInto for ConversationConfiguration { cfg.ciphersuite = mls_ciphersuite.into(); } - cfg.per_domain_trust_anchors = self.per_domain_trust_anchors.into_iter().map(|a| a.into()).collect(); - Ok(cfg) } } @@ -1688,47 +1649,6 @@ impl CoreCrypto { ) } - /// Returns: [`WasmCryptoResult`] - /// - /// see [core_crypto::mls::MlsCentral::update_trust_anchors_from_conversation] - pub fn update_trust_anchors_from_conversation( - &self, - conversation_id: ConversationId, - remove_domain_names: Box<[js_sys::JsString]>, - add_trust_anchors: js_sys::Array, - ) -> Promise { - let this = self.inner.clone(); - future_to_promise( - async move { - let add_trust_anchors = if add_trust_anchors.is_null() { - vec![] - } else { - add_trust_anchors - .into_iter() - .map(|js_anchor| -> WasmCryptoResult { - Ok(serde_wasm_bindgen::from_value(js_anchor)?) - }) - .map(|result| Ok(result?.into())) - .collect::>>()? - }; - let commit = this - .write() - .await - .update_trust_anchors_from_conversation( - &conversation_id.to_vec(), - remove_domain_names.iter().map(String::from).collect(), - add_trust_anchors, - ) - .await - .map_err(CoreCryptoError::from)?; - let commit: CommitBundle = commit.try_into()?; - - WasmCryptoResult::Ok(serde_wasm_bindgen::to_value(&commit)?) - } - .err_into(), - ) - } - /// Returns: [`WasmCryptoResult`] /// /// see [core_crypto::mls::MlsCentral::new_add_proposal] diff --git a/crypto/src/lib.rs b/crypto/src/lib.rs index 2002b42bad..076077c042 100644 --- a/crypto/src/lib.rs +++ b/crypto/src/lib.rs @@ -89,7 +89,7 @@ pub mod prelude { handshake::{MlsCommitBundle, MlsConversationCreationMessage, MlsProposalBundle}, *, }, - credential::{trust_anchor::PerDomainTrustAnchor, typ::MlsCredentialType, x509::CertificateBundle}, + credential::{typ::MlsCredentialType, x509::CertificateBundle}, external_commit::MlsConversationInitBundle, proposal::{MlsProposal, MlsProposalRef}, MlsCentral, diff --git a/crypto/src/mls/client/key_package.rs b/crypto/src/mls/client/key_package.rs index 0c352bb49c..e79cfe7542 100644 --- a/crypto/src/mls/client/key_package.rs +++ b/crypto/src/mls/client/key_package.rs @@ -357,7 +357,7 @@ impl MlsCentral { #[cfg(test)] pub mod tests { - use openmls::prelude::{ExtensionType, KeyPackage, KeyPackageIn, KeyPackageRef, ProtocolVersion}; + use openmls::prelude::{KeyPackage, KeyPackageIn, KeyPackageRef, ProtocolVersion}; use openmls_traits::types::VerifiableCiphersuite; use openmls_traits::OpenMlsCryptoProvider; use wasm_bindgen_test::*; @@ -561,10 +561,7 @@ pub mod tests { .collect::>() ); assert!(kp.leaf_node().capabilities().proposals().is_empty()); - assert_eq!( - kp.leaf_node().capabilities().extensions(), - &[ExtensionType::PerDomainTrustAnchor] - ); + assert!(kp.leaf_node().capabilities().extensions().is_empty()); assert_eq!( kp.leaf_node().capabilities().credentials(), MlsConversationConfiguration::DEFAULT_SUPPORTED_CREDENTIALS diff --git a/crypto/src/mls/conversation/config.rs b/crypto/src/mls/conversation/config.rs index 7961539ba0..df2756575b 100644 --- a/crypto/src/mls/conversation/config.rs +++ b/crypto/src/mls/conversation/config.rs @@ -20,16 +20,13 @@ //! when joining one by Welcome or external commit use openmls::prelude::{ - Capabilities, Credential, CredentialType, ExtensionType, ExternalSender, ProtocolVersion, - RequiredCapabilitiesExtension, SenderRatchetConfiguration, SignaturePublicKey, WireFormatPolicy, - PURE_CIPHERTEXT_WIRE_FORMAT_POLICY, PURE_PLAINTEXT_WIRE_FORMAT_POLICY, + Capabilities, Credential, CredentialType, ExternalSender, ProtocolVersion, RequiredCapabilitiesExtension, + SenderRatchetConfiguration, SignaturePublicKey, WireFormatPolicy, PURE_CIPHERTEXT_WIRE_FORMAT_POLICY, + PURE_PLAINTEXT_WIRE_FORMAT_POLICY, }; use openmls_traits::types::Ciphersuite; use serde::{Deserialize, Serialize}; -use mls_crypto_provider::MlsCryptoProvider; - -use crate::mls::credential::trust_anchor::PerDomainTrustAnchor; use crate::prelude::{CryptoResult, MlsCiphersuite}; /// Sets the config in OpenMls for the oldest possible epoch(past current) that a message can be decrypted @@ -51,8 +48,6 @@ pub struct MlsConversationConfiguration { pub external_senders: Vec, /// Implementation specific configuration pub custom: MlsCustomConfiguration, - /// Domain trust anchors to set in the conversation's extensions - pub per_domain_trust_anchors: Vec, } impl MlsConversationConfiguration { @@ -82,17 +77,7 @@ impl MlsConversationConfiguration { /// Generates an `MlsGroupConfig` from this configuration #[inline(always)] - pub fn as_openmls_default_configuration( - &self, - backend: &MlsCryptoProvider, - ) -> CryptoResult { - let trust_certificates = self.per_domain_trust_anchors.clone().into_iter().try_fold( - vec![], - |mut acc, c| -> CryptoResult> { - acc.push(c.try_as_checked_openmls_trust_anchor(backend, None)?); - Ok(acc) - }, - )?; + pub fn as_openmls_default_configuration(&self) -> CryptoResult { let crypto_config = openmls::prelude::CryptoConfig { version: Self::DEFAULT_PROTOCOL_VERSION, ciphersuite: self.ciphersuite.into(), @@ -109,7 +94,6 @@ impl MlsConversationConfiguration { self.custom.maximum_forward_distance, )) .use_ratchet_tree_extension(true) - .trust_certificates(trust_certificates) .external_senders(self.external_senders.clone()) .crypto_config(crypto_config) .build()) @@ -120,18 +104,14 @@ impl MlsConversationConfiguration { Capabilities::new( Some(&[Self::DEFAULT_PROTOCOL_VERSION]), Some(Self::DEFAULT_SUPPORTED_CIPHERSUITES), - Some(&[ExtensionType::PerDomainTrustAnchor]), + Some(&[]), Some(&[]), Some(Self::DEFAULT_SUPPORTED_CREDENTIALS), ) } fn default_required_capabilities(&self) -> RequiredCapabilitiesExtension { - RequiredCapabilitiesExtension::new( - &[ExtensionType::PerDomainTrustAnchor], - &[], - Self::DEFAULT_SUPPORTED_CREDENTIALS, - ) + RequiredCapabilitiesExtension::new(&[], &[], Self::DEFAULT_SUPPORTED_CREDENTIALS) } /// Parses supplied key from Delivery Service in order to build back an [ExternalSender] @@ -201,7 +181,7 @@ impl From for WireFormatPolicy { #[cfg(test)] pub mod tests { use crate::{prelude::MlsConversationConfiguration, test_utils::*}; - use openmls::{prelude::ExtensionType, prelude::ProtocolVersion}; + use openmls::prelude::ProtocolVersion; use openmls_traits::types::VerifiableCiphersuite; use wasm_bindgen_test::*; @@ -223,7 +203,7 @@ pub mod tests { let capabilities = group.group.group_context_extensions().required_capabilities().unwrap(); // see https://www.rfc-editor.org/rfc/rfc9420.html#section-11.1 - assert_eq!(capabilities.extension_types(), &[ExtensionType::PerDomainTrustAnchor]); + assert!(capabilities.extension_types().is_empty()); assert!(capabilities.proposal_types().is_empty()); assert_eq!( capabilities.credential_types(), @@ -265,11 +245,8 @@ pub mod tests { // Proposals MUST be empty since we support all the default ones assert!(creator_capabilities.proposals().is_empty()); - // Extensions MUST only contain our custom 'PerDomainTrustAnchor' extension - assert_eq!( - creator_capabilities.extensions(), - &[ExtensionType::PerDomainTrustAnchor] - ); + // Extensions MUST only contain non-default extension (i.e. empty for now) + assert!(creator_capabilities.extensions().is_empty(),); // To prevent downgrade attacks, Credentials should just contain the current assert_eq!( diff --git a/crypto/src/mls/conversation/decrypt.rs b/crypto/src/mls/conversation/decrypt.rs index ebea3de9dc..e7fd08957f 100644 --- a/crypto/src/mls/conversation/decrypt.rs +++ b/crypto/src/mls/conversation/decrypt.rs @@ -153,10 +153,6 @@ impl MlsConversation { // getting the pending has to be done before `merge_staged_commit` otherwise it's wiped out let pending_commit = self.group.pending_commit().cloned(); - let old_group_context = self.group.export_group_context(); - let commit_group_context = staged_commit.staged_context(); - Self::validate_received_trust_anchors(old_group_context, commit_group_context, backend)?; - self.group .merge_staged_commit(backend, *staged_commit.clone()) .await diff --git a/crypto/src/mls/conversation/leaf_node_validation.rs b/crypto/src/mls/conversation/leaf_node_validation.rs index a0558acaaf..fe443e8c2f 100644 --- a/crypto/src/mls/conversation/leaf_node_validation.rs +++ b/crypto/src/mls/conversation/leaf_node_validation.rs @@ -21,14 +21,18 @@ pub mod tests { /// The validity of a LeafNode needs to be verified at the following stages: /// When a LeafNode is downloaded in a KeyPackage, before it is used to add the client to the group - #[apply(all_cred_cipher)] - #[wasm_bindgen_test] - pub async fn should_validate_leaf_node_when_adding(case: TestCase) { + // #[apply(all_cred_cipher)] + // #[wasm_bindgen_test] + #[async_std::test] + pub async fn should_validate_leaf_node_when_adding(/*case: TestCase*/) { + let case = TestCase::default(); run_test_with_client_ids( case.clone(), ["alice", "bob"], move |[mut alice_central, bob_central]| { Box::pin(async move { + let expiration_time = 14; + let start = fluvio_wasm_timer::Instant::now(); let id = conversation_id(); alice_central .new_conversation(&id, case.credential_type, case.cfg.clone()) @@ -36,7 +40,15 @@ pub mod tests { .unwrap(); // should fail when creating Add proposal - let invalid_kp = bob_central.new_invalid_keypackage(&case).await; + let invalid_kp = bob_central.new_keypackage(&case, Lifetime::new(expiration_time)).await; + + // Give time to the KeyPackage to expire + let expiration_time = core::time::Duration::from_secs(expiration_time); + let elapsed = start.elapsed(); + if expiration_time > elapsed { + async_std::task::sleep(expiration_time - elapsed + core::time::Duration::from_secs(1)) + .await; + } let proposal_creation = alice_central.new_add_proposal(&id, invalid_kp).await; assert!(matches!( @@ -48,7 +60,19 @@ pub mod tests { assert!(alice_central.pending_proposals(&id).await.is_empty()); // should fail when creating Add commits - let invalid_kp = bob_central.new_invalid_keypackage(&case).await; + let expiration_time = 14; + let start = fluvio_wasm_timer::Instant::now(); + + let invalid_kp = bob_central.new_keypackage(&case, Lifetime::new(expiration_time)).await; + + // Give time to the KeyPackage to expire + let expiration_time = core::time::Duration::from_secs(expiration_time); + let elapsed = start.elapsed(); + if expiration_time > elapsed { + async_std::task::sleep(expiration_time - elapsed + core::time::Duration::from_secs(1)) + .await; + } + let commit_creation = alice_central .add_members_to_conversation(&id, vec![invalid_kp.into()]) .await; diff --git a/crypto/src/mls/conversation/mod.rs b/crypto/src/mls/conversation/mod.rs index f96cca026c..80a33ce38a 100644 --- a/crypto/src/mls/conversation/mod.rs +++ b/crypto/src/mls/conversation/mod.rs @@ -105,7 +105,7 @@ impl MlsConversation { let group = MlsGroup::new_with_group_id( backend, &cb.signature_key, - &configuration.as_openmls_default_configuration(backend)?, + &configuration.as_openmls_default_configuration()?, openmls::prelude::GroupId::from_slice(id.as_slice()), cb.to_mls_credential_with_key(), ) diff --git a/crypto/src/mls/conversation/welcome.rs b/crypto/src/mls/conversation/welcome.rs index 270f9bef74..17b5da356f 100644 --- a/crypto/src/mls/conversation/welcome.rs +++ b/crypto/src/mls/conversation/welcome.rs @@ -90,7 +90,7 @@ impl MlsConversation { backend: &mut MlsCryptoProvider, mls_groups: &mut GroupStore, ) -> CryptoResult { - let mls_group_config = configuration.as_openmls_default_configuration(backend)?; + let mls_group_config = configuration.as_openmls_default_configuration()?; let group = MlsGroup::new_from_welcome(backend, &mls_group_config, welcome, None).await; diff --git a/crypto/src/mls/credential/mod.rs b/crypto/src/mls/credential/mod.rs index bdde9732b5..7684022889 100644 --- a/crypto/src/mls/credential/mod.rs +++ b/crypto/src/mls/credential/mod.rs @@ -4,7 +4,6 @@ use std::cmp::Ordering; use std::hash::{Hash, Hasher}; pub(crate) mod ext; -pub(crate) mod trust_anchor; pub(crate) mod typ; pub(crate) mod x509; diff --git a/crypto/src/mls/credential/trust_anchor.rs b/crypto/src/mls/credential/trust_anchor.rs deleted file mode 100644 index 80be11caf2..0000000000 --- a/crypto/src/mls/credential/trust_anchor.rs +++ /dev/null @@ -1,1699 +0,0 @@ -use mls_crypto_provider::MlsCryptoProvider; -use openmls::prelude::group_context::GroupContext; -use openmls_traits::OpenMlsCryptoProvider; -use openmls_x509_credential::X509Ext; -use x509_cert::{der::Decode, Certificate, PkiPath}; - -use crate::{ - mls::{ - client::Client, - conversation::{group_info::MlsGroupInfoBundle, handshake::MlsCommitBundle, ConversationId}, - MlsCentral, - }, - prelude::MlsConversation, - MlsError, -}; -use crate::{CryptoError, CryptoResult}; - -/// A wrapper containing the configuration for trust anchors to be added in the group's context -/// extensions -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct PerDomainTrustAnchor { - /// Domain name in which the trust anchor belongs to - pub domain_name: String, - /// PEM encoded certificate chain - pub intermediate_certificate_chain: String, -} - -impl TryFrom<&openmls::extensions::PerDomainTrustAnchor> for PerDomainTrustAnchor { - type Error = CryptoError; - - fn try_from(value: &openmls::extensions::PerDomainTrustAnchor) -> Result { - let pems: Vec<_> = value - .certificate_chain() - .iter() - .map(|cert| pem::Pem::new("CERTIFICATE", &cert[..])) - .collect(); - Ok(Self { - domain_name: std::str::from_utf8(value.domain_name())?.to_string(), - intermediate_certificate_chain: pem::encode_many(&pems), - }) - } -} - -impl PerDomainTrustAnchor { - /// Converts to the OpenMls's counterpart of this struct - /// It performs validation first - pub fn try_as_checked_openmls_trust_anchor( - self, - backend: &MlsCryptoProvider, - group_context: Option<&GroupContext>, - ) -> CryptoResult { - let certificate_chain = self.validate(backend, group_context)?; - Ok(openmls::extensions::PerDomainTrustAnchor::new( - self.domain_name.into(), - openmls::prelude::CredentialType::X509, - certificate_chain, - ) - .map_err(MlsError::from)?) - } - - /// Validates the trust anchor and return its encoded chain encoded to der. - fn validate( - &self, - backend: &MlsCryptoProvider, - group_context: Option<&GroupContext>, - ) -> CryptoResult>> { - // parse PEM - let certificate_chain = pem::parse_many(&self.intermediate_certificate_chain)? - .iter() - .map(|p| Certificate::from_der(p.contents())) - .collect::>()?; - - // verify domain_name is unique - if let Some(group_context) = group_context { - if group_context - .extensions() - .per_domain_trust_anchors() - .is_some_and(|anchors| { - anchors - .iter() - .any(|anchor| String::from_utf8_lossy(anchor.domain_name()) == self.domain_name) - }) - { - return Err(CryptoError::DuplicateDomainName); - } - } - - // empty chains are not allowed - let leaf_cert = certificate_chain.first().ok_or(CryptoError::InvalidCertificateChain)?; - - // validate domain in the leaf matches with the one supplied - let domain_names = extract_domain_names(leaf_cert)?; - if !domain_names.contains(&self.domain_name) { - return Err(CryptoError::DomainNamesDontMatch); - } - - // verify the whole chain - let root_cert = certificate_chain - .iter() - .map(Ok) - .reduce( - |child, parent| -> Result<&Certificate, openmls_traits::types::CryptoError> { - let child = child?; - let parent = parent?; - child.is_valid()?; - child.is_signed_by(backend.crypto(), parent)?; - Ok(parent) - }, - ) - .unwrap() - .map_err(MlsError::from)?; - // ensure that the root is also valid - root_cert.is_valid().map_err(MlsError::from)?; - - check_root_in_trust_store(root_cert)?; - - let encoded_chain = pem::parse_many(&self.intermediate_certificate_chain)? - .into_iter() - .map(|p| p.into_contents()) - .collect::>(); - Ok(encoded_chain) - } -} - -/// Checks the root cert against the trust store. In wasm maybe use webpki-roots (https://github.com/rustls/webpki-roots) crate -fn check_root_in_trust_store(_root: &Certificate) -> CryptoResult<()> { - // TODO: verify root certificate is valid in the device's trust store - Ok(()) -} - -fn is_trust_anchor_new(new_anchor: &openmls::prelude::PerDomainTrustAnchor, old_group_context: &GroupContext) -> bool { - let old_trust_anchor = old_group_context.extensions().per_domain_trust_anchors(); - - let no_old_trust_anchor = old_trust_anchor.is_none(); - let empty_old_trust_anchor = old_trust_anchor.map(|a| a.is_empty()).unwrap_or_default(); - - let anchor_is_new = old_trust_anchor.is_some_and(|old_anchor| { - !old_anchor - .iter() - .any(|old_anchor| old_anchor.certificate_chain() == new_anchor.certificate_chain()) - }); - - no_old_trust_anchor || empty_old_trust_anchor || anchor_is_new -} - -impl MlsConversation { - /// Validates the certificate chain - pub(crate) fn validate_received_trust_anchors( - old_group_context: &GroupContext, - commit_group_context: &GroupContext, - backend: &MlsCryptoProvider, - ) -> CryptoResult<()> { - if let Some(new_anchors) = commit_group_context.extensions().per_domain_trust_anchors() { - // find new anchors - new_anchors - .iter() - .filter(|new_anchor| is_trust_anchor_new(new_anchor, old_group_context)) - .try_for_each(|anchor| -> CryptoResult<()> { - let anchor = PerDomainTrustAnchor::try_from(anchor)?; - // the domain will obviously be in the context and will be already applied by - // other client, so the domain uniqueness should not be validated here - anchor.validate(backend, None)?; - Ok(()) - })?; - } - Ok(()) - } - - /// Validates the trust anchors update against the current state of the group context and - /// returns a new list of anchors that should be set for the group's extension - fn compute_anchors_for_next_epoch( - &self, - remove_domain_names: Vec, - add_trust_anchors: Vec, - backend: &MlsCryptoProvider, - ) -> CryptoResult> { - if remove_domain_names.is_empty() && add_trust_anchors.is_empty() { - return Err(CryptoError::EmptyTrustAnchorUpdate); - } - - let context = self.group.export_group_context(); - let extensions = context.extensions(); - let mut anchors = extensions - .per_domain_trust_anchors() - .map(|anchors| { - anchors - .iter() - .map(PerDomainTrustAnchor::try_from) - .collect::>>() - }) - .unwrap_or_else(|| Ok(Vec::new()))?; - - let chain_count = anchors.len(); - - // remove anchors - anchors.retain(|anchor| !remove_domain_names.contains(&anchor.domain_name)); - - // check if all to remove exists - if chain_count != anchors.len() + remove_domain_names.len() { - return Err(CryptoError::DomainNameNotFound); - } - - anchors.iter().try_for_each(|a| { - // check for duplicate anchors to be added - if add_trust_anchors.iter().any(|n| a.domain_name == n.domain_name) { - return Err(CryptoError::DuplicateDomainName); - } - // check if any new chain is already in the group's context - if add_trust_anchors - .iter() - .any(|n| n.intermediate_certificate_chain == a.intermediate_certificate_chain) - { - return Err(CryptoError::DuplicateCertificateChain); - } - Ok(()) - })?; - let new_anchors = anchors - .into_iter() - .chain(add_trust_anchors.into_iter()) - .map(|anchor| anchor.try_as_checked_openmls_trust_anchor(backend, None)) - .collect::>>()?; - Ok(new_anchors) - } - - /// see [MlsCentral::update_trust_anchors_from_conversation] - #[cfg_attr(test, crate::durable)] - pub(crate) async fn update_trust_anchors( - &mut self, - client: &Client, - remove_domain_names: Vec, - add_trust_anchors: Vec, - backend: &MlsCryptoProvider, - ) -> CryptoResult { - // parse back to mls and validate the anchors - let context = self.group.export_group_context(); - let mut extensions = context.extensions().clone(); - let new_anchors = self.compute_anchors_for_next_epoch(remove_domain_names, add_trust_anchors, backend)?; - extensions.add_or_replace(openmls::prelude::Extension::PerDomainTrustAnchor(new_anchors)); - - // update the group extension through a GCE commit - let cs = self.ciphersuite(); - let ct = self.own_credential_type()?; - let signer = &client - .find_most_recent_credential_bundle(cs.signature_algorithm(), ct) - .ok_or(CryptoError::MlsNotInitialized)? - .signature_key; - let (commit, welcome, gi) = self - .group - .update_extensions(backend, signer, extensions) - .await - .map_err(MlsError::from)?; - - // SAFETY: This should be safe as updating extensions always generates a new commit - let gi = gi.ok_or(CryptoError::ImplementationError)?; - let group_info = MlsGroupInfoBundle::try_new_full_plaintext(gi)?; - - self.persist_group_when_changed(backend, false).await?; - - Ok(MlsCommitBundle { - commit, - welcome, - group_info, - }) - } -} - -impl MlsCentral { - /// Updates the trust anchors for a conversation - /// - /// # Arguments - /// * `id` - group/conversation id - /// * `remove_domain_names` - domains to be removed from the group - /// * `add_trust_anchors` - new trust anchors to be added to the group - /// - /// # Return type - /// An struct containing a welcome(optional, will be present only if there's pending add - /// proposals in the store), a message with the commit to fan out to other clients and - /// the group info will be returned on successful call. - /// - /// # Errors - /// If the authorisation callback is set, an error can be caused when the authorization fails. - /// Other errors are KeyStore and OpenMls errors: - pub async fn update_trust_anchors_from_conversation( - &mut self, - id: &ConversationId, - remove_domain_names: Vec, - add_trust_anchors: Vec, - ) -> CryptoResult { - if let Some(callbacks) = self.callbacks.as_ref() { - let client_id = self.mls_client()?.id().clone(); - if !callbacks.authorize(id.clone(), client_id).await { - return Err(CryptoError::Unauthorized); - } - } - - self.get_conversation(id) - .await? - .write() - .await - .update_trust_anchors( - self.mls_client()?, - remove_domain_names, - add_trust_anchors, - &self.mls_backend, - ) - .await - } -} - -pub(crate) fn extract_domain_names(certificate: &Certificate) -> CryptoResult> { - let common_name = certificate - .tbs_certificate - .subject - .0 - .iter() - .flat_map(|n| n.0.iter()) - .find_map(|attr| { - if attr.oid.as_bytes() == oid_registry::OID_X509_COMMON_NAME.as_bytes() { - Some(attr.value.value()) - } else { - None - } - }) - .map(|bytes| String::from_utf8(bytes.to_owned())) - .transpose()?; - - let san = if let Some(extensions) = certificate.tbs_certificate.extensions.as_ref() { - extensions - .iter() - .find(|e| e.extn_id.as_bytes() == oid_registry::OID_X509_EXT_SUBJECT_ALT_NAME.as_bytes()) - .map(|e| x509_cert::ext::pkix::SubjectAltName::from_der(e.extn_value.as_bytes())) - .transpose()? - } else { - None - }; - - let dns_names: Vec<_> = san - .into_iter() - .flat_map(|san| { - san.0 - .iter() - .filter_map(|n| match n { - x509_cert::ext::pkix::name::GeneralName::DnsName(ia5_str) => Some(ia5_str.to_string()), - _ => None, - }) - .collect::>() - }) - .chain(common_name) - .collect(); - - if dns_names.is_empty() { - Err(CryptoError::DomainNameNotFound) - } else { - Ok(dns_names) - } -} - -#[cfg(test)] -mod tests { - use std::time::Duration; - - use crate::{ - mls::{ - conversation::{ - config::MlsConversationConfiguration, handshake::MlsConversationCreationMessage, ConversationId, - }, - credential::typ::MlsCredentialType, - MlsCentral, - }, - test_utils::{conversation_id, TestCase}, - CryptoResult, - }; - - mod domain_name_extraction { - use crate::{mls::credential::trust_anchor::extract_domain_names, test_utils::TestCase}; - - use super::*; - use crate::{ - test_utils::{x509::*, *}, - CryptoError, - }; - use openmls_traits::types::Ciphersuite; - use wasm_bindgen_test::*; - - wasm_bindgen_test_configure!(run_in_browser); - - #[apply(all_cred_cipher)] - #[wasm_bindgen_test] - fn should_extract_domain_name(case: TestCase) { - let ciphersuite: Ciphersuite = case.cfg.ciphersuite.into(); - let cert = create_single_certificate( - CertificateParams { - org: "Project Zeta GmBh".to_string(), - common_name: None, - domain: Some("wire.com".to_string()), - expiration: Duration::from_secs(10), - }, - ciphersuite.into(), - false, - ); - - let domain_names = extract_domain_names(&cert).unwrap(); - assert_eq!(domain_names[0], "wire.com"); - } - - #[apply(all_cred_cipher)] - #[wasm_bindgen_test] - fn should_extract_domain_name_common_name(case: TestCase) { - let ciphersuite: Ciphersuite = case.cfg.ciphersuite.into(); - let cert = create_single_certificate( - CertificateParams { - org: "Project Zeta GmBh".to_string(), - common_name: Some("wire.com".to_string()), - domain: None, - expiration: Duration::from_secs(10), - }, - ciphersuite.into(), - false, - ); - - let domain_names = extract_domain_names(&cert).unwrap(); - assert_eq!(domain_names[0], "wire.com"); - } - - #[apply(all_cred_cipher)] - #[wasm_bindgen_test] - fn should_fail_extract_domain_name(case: TestCase) { - let ciphersuite: Ciphersuite = case.cfg.ciphersuite.into(); - let cert = create_single_certificate( - CertificateParams { - org: "Project Zeta GmBh".to_string(), - common_name: None, - domain: None, - expiration: Duration::from_secs(10), - }, - ciphersuite.into(), - false, - ); - - let err = extract_domain_names(&cert).unwrap_err(); - assert!(matches!(err, CryptoError::DomainNameNotFound)); - } - } - - mod on_group_creation { - - use super::*; - use crate::{ - mls::credential::trust_anchor::PerDomainTrustAnchor, - test_utils::{x509::*, *}, - CryptoError, MlsError, - }; - use openmls::prelude::CryptoError as MlsCryptoError; - use openmls_traits::types::Ciphersuite; - use wasm_bindgen_test::*; - - wasm_bindgen_test_configure!(run_in_browser); - - #[apply(all_cred_cipher)] - #[wasm_bindgen_test] - pub async fn should_create_group_with_trust_anchors(mut case: TestCase) { - run_test_with_client_ids( - case.clone(), - ["alice", "bob"], - move |[mut alice_central, mut bob_central]| { - Box::pin(async move { - let ciphersuite: Ciphersuite = case.cfg.ciphersuite.into(); - let anchors = create_intermediate_certificates( - CertificateParams { - org: "Project Zeta GmBh".to_string(), - common_name: Some("wire.com".to_string()), - domain: Some("wire.com".to_string()), - expiration: Duration::from_secs(10), - }, - ciphersuite.into(), - ); - case.cfg.per_domain_trust_anchors = vec![anchors.into()]; - let id = create_group( - case.cfg.clone(), - case.credential_type, - &mut alice_central, - &mut bob_central, - &case, - ) - .await - .unwrap(); - - // both must have the anchors in the extensions - let alice_anchors = alice_central - .get_conversation_unchecked(&id) - .await - .per_domain_trust_anchors(); - let bob_anchors = bob_central - .get_conversation_unchecked(&id) - .await - .per_domain_trust_anchors(); - assert_eq!(alice_anchors.len(), 1); - assert_eq!(alice_anchors, bob_anchors); - alice_anchors[0].validate(&alice_central.mls_backend, None).unwrap(); - }) - }, - ) - .await; - } - - #[apply(all_cred_cipher)] - #[wasm_bindgen_test] - pub async fn should_fail_create_group_with_expired_certs(mut case: TestCase) { - run_test_with_client_ids( - case.clone(), - ["alice", "bob"], - move |[mut alice_central, mut bob_central]| { - Box::pin(async move { - let ciphersuite: Ciphersuite = case.cfg.ciphersuite.into(); - let anchors = create_intermediate_certificates( - CertificateParams { - org: "Project Zeta GmBh".to_string(), - common_name: Some("wire.com".to_string()), - domain: Some("wire.com".to_string()), - expiration: Duration::ZERO, - }, - ciphersuite.into(), - ); - case.cfg.per_domain_trust_anchors = vec![anchors.into()]; - let error = create_group( - case.cfg.clone(), - case.credential_type, - &mut alice_central, - &mut bob_central, - &case, - ) - .await - .unwrap_err(); - - assert!(matches!( - error, - CryptoError::MlsError(MlsError::MlsCryptoError(MlsCryptoError::ExpiredCertificate)) - )); - }) - }, - ) - .await; - } - - #[apply(all_cred_cipher)] - #[wasm_bindgen_test] - pub async fn should_create_group_single_cert(mut case: TestCase) { - run_test_with_client_ids( - case.clone(), - ["alice", "bob"], - move |[mut alice_central, mut bob_central]| { - Box::pin(async move { - let ciphersuite: Ciphersuite = case.cfg.ciphersuite.into(); - let cert = create_single_certificate( - CertificateParams { - org: "Project Zeta GmBh".to_string(), - common_name: Some("wire.com".to_string()), - domain: Some("wire.com".to_string()), - expiration: Duration::from_secs(10), - }, - ciphersuite.into(), - true, - ); - case.cfg.per_domain_trust_anchors = vec![cert.into()]; - let id = create_group( - case.cfg.clone(), - case.credential_type, - &mut alice_central, - &mut bob_central, - &case, - ) - .await - .unwrap(); - // both must have the anchors in the extensions - let alice_anchors = alice_central - .get_conversation_unchecked(&id) - .await - .per_domain_trust_anchors(); - let bob_anchors = bob_central - .get_conversation_unchecked(&id) - .await - .per_domain_trust_anchors(); - assert_eq!(alice_anchors.len(), 1); - assert_eq!(alice_anchors, bob_anchors); - alice_anchors[0].validate(&alice_central.mls_backend, None).unwrap(); - }) - }, - ) - .await; - } - - #[apply(all_cred_cipher)] - #[wasm_bindgen_test] - pub async fn should_fail_create_group_invalid_chain(mut case: TestCase) { - run_test_with_client_ids( - case.clone(), - ["alice", "bob"], - move |[mut alice_central, mut bob_central]| { - Box::pin(async move { - let ciphersuite: Ciphersuite = case.cfg.ciphersuite.into(); - let cert = create_single_certificate( - CertificateParams { - org: "World Domination Inc".to_string(), - common_name: Some("World Domination".to_string()), - domain: None, - expiration: Duration::from_secs(10), - }, - ciphersuite.into(), - false, - ); - let ca = create_single_certificate( - CertificateParams { - org: "Project Zeta GmBh".to_string(), - common_name: Some("wire.com".to_string()), - domain: Some("wire.com".to_string()), - expiration: Duration::from_secs(10), - }, - ciphersuite.into(), - true, - ); - case.cfg.per_domain_trust_anchors = vec![vec![cert, ca].into()]; - let error = dbg!(create_group( - case.cfg.clone(), - case.credential_type, - &mut alice_central, - &mut bob_central, - &case - ) - .await - .unwrap_err()); - - assert!(matches!( - error, - CryptoError::MlsError(MlsError::MlsCryptoError(MlsCryptoError::InvalidSignature)) - )); - }) - }, - ) - .await; - } - - #[apply(all_cred_cipher)] - #[wasm_bindgen_test] - pub async fn should_fail_create_group_unmatched_domains(mut case: TestCase) { - run_test_with_client_ids( - case.clone(), - ["alice", "bob"], - move |[mut alice_central, mut bob_central]| { - Box::pin(async move { - let ciphersuite: Ciphersuite = case.cfg.ciphersuite.into(); - let mut anchor: PerDomainTrustAnchor = create_intermediate_certificates( - CertificateParams { - org: "Project Zeta GmBh".to_string(), - common_name: Some("wire.com".to_string()), - domain: Some("wire.com".to_string()), - expiration: Duration::ZERO, - }, - ciphersuite.into(), - ) - .into(); - anchor.domain_name = "wrong.domain.cc".to_string(); - case.cfg.per_domain_trust_anchors = vec![anchor]; - let error = create_group( - case.cfg.clone(), - case.credential_type, - &mut alice_central, - &mut bob_central, - &case, - ) - .await - .unwrap_err(); - - assert!(matches!(error, CryptoError::DomainNamesDontMatch)); - }) - }, - ) - .await; - } - - #[apply(all_cred_cipher)] - #[wasm_bindgen_test] - pub async fn should_fail_create_group_domain_not_found(mut case: TestCase) { - run_test_with_client_ids( - case.clone(), - ["alice", "bob"], - move |[mut alice_central, mut bob_central]| { - Box::pin(async move { - let ciphersuite: Ciphersuite = case.cfg.ciphersuite.into(); - // No domain at all - let anchor: PerDomainTrustAnchor = create_intermediate_certificates( - CertificateParams { - org: "Project Zeta GmBh".to_string(), - common_name: None, - domain: None, - expiration: Duration::ZERO, - }, - ciphersuite.into(), - ) - .into(); - case.cfg.per_domain_trust_anchors = vec![anchor]; - let error = create_group( - case.cfg.clone(), - case.credential_type, - &mut alice_central, - &mut bob_central, - &case, - ) - .await - .unwrap_err(); - - assert!(matches!(error, CryptoError::DomainNameNotFound)); - }) - }, - ) - .await; - } - } - - mod update_anchors { - - use super::*; - use crate::{ - test_utils::{x509::*, *}, - CryptoError, MlsError, - }; - use openmls::prelude::CryptoError as MlsCryptoError; - use openmls_traits::types::Ciphersuite; - use wasm_bindgen_test::*; - - wasm_bindgen_test_configure!(run_in_browser); - - #[apply(all_cred_cipher)] - #[wasm_bindgen_test] - pub async fn should_add_anchors_to_group(case: TestCase) { - run_test_with_client_ids( - case.clone(), - ["alice", "bob"], - move |[mut alice_central, mut bob_central]| { - Box::pin(async move { - let ciphersuite: Ciphersuite = case.cfg.ciphersuite.into(); - let anchors = create_intermediate_certificates( - CertificateParams { - org: "Project Zeta GmBh".to_string(), - common_name: Some("wire.com".to_string()), - domain: Some("wire.com".to_string()), - expiration: Duration::from_secs(10), - }, - ciphersuite.into(), - ); - let id = create_group( - case.cfg.clone(), - case.credential_type, - &mut alice_central, - &mut bob_central, - &case, - ) - .await - .unwrap(); - // anchors should not be present - assert!(alice_central - .get_conversation_unchecked(&id) - .await - .extensions() - .per_domain_trust_anchors() - .is_none()); - - // adding anchors to group - let commit_bundle = alice_central - .update_trust_anchors_from_conversation(&id, vec![], vec![anchors.into()]) - .await - .unwrap(); - alice_central.commit_accepted(&id).await.unwrap(); - // bob parses the commit - bob_central - .decrypt_message(&id, &commit_bundle.commit.to_bytes().unwrap()) - .await - .unwrap(); - - // both must have the anchors in the extensions - let alice_anchors = alice_central - .get_conversation_unchecked(&id) - .await - .per_domain_trust_anchors(); - let bob_anchors = bob_central - .get_conversation_unchecked(&id) - .await - .per_domain_trust_anchors(); - assert_eq!(alice_anchors.len(), 1); - assert_eq!(alice_anchors, bob_anchors); - alice_anchors[0].validate(&alice_central.mls_backend, None).unwrap(); - }) - }, - ) - .await; - } - - #[apply(all_cred_cipher)] - #[wasm_bindgen_test] - pub async fn should_add_anchors_to_group_with_anchors(mut case: TestCase) { - run_test_with_client_ids( - case.clone(), - ["alice", "bob"], - move |[mut alice_central, mut bob_central]| { - Box::pin(async move { - let ciphersuite: Ciphersuite = case.cfg.ciphersuite.into(); - let anchors = create_intermediate_certificates( - CertificateParams { - org: "Project Zeta GmBh".to_string(), - common_name: Some("wire.com".to_string()), - domain: Some("wire.com".to_string()), - expiration: Duration::from_secs(10), - }, - ciphersuite.into(), - ); - case.cfg.per_domain_trust_anchors = vec![anchors.into()]; - let id = create_group( - case.cfg.clone(), - case.credential_type, - &mut alice_central, - &mut bob_central, - &case, - ) - .await - .unwrap(); - let new_anchors = create_intermediate_certificates( - CertificateParams { - org: "Project Zeta 2 GmBh".to_string(), - common_name: Some("wire2.com".to_string()), - domain: Some("wire2.com".to_string()), - expiration: Duration::from_secs(10), - }, - ciphersuite.into(), - ); - - // adding anchors to group - let commit_bundle = alice_central - .update_trust_anchors_from_conversation(&id, vec![], vec![new_anchors.into()]) - .await - .unwrap(); - alice_central.commit_accepted(&id).await.unwrap(); - // bob parses the commit - bob_central - .decrypt_message(&id, &commit_bundle.commit.to_bytes().unwrap()) - .await - .unwrap(); - - // both must have the anchors in the extensions - let alice_anchors = alice_central - .get_conversation_unchecked(&id) - .await - .per_domain_trust_anchors(); - let bob_anchors = bob_central - .get_conversation_unchecked(&id) - .await - .per_domain_trust_anchors(); - assert_eq!(alice_anchors.len(), 2); - assert_eq!(alice_anchors, bob_anchors); - alice_anchors[0].validate(&alice_central.mls_backend, None).unwrap(); - alice_anchors[1].validate(&alice_central.mls_backend, None).unwrap(); - assert_eq!(alice_anchors[0].domain_name, "wire.com"); - assert_eq!(alice_anchors[1].domain_name, "wire2.com"); - }) - }, - ) - .await; - } - - #[apply(all_cred_cipher)] - #[wasm_bindgen_test] - pub async fn should_fail_add_duplicate_anchors_to_group(mut case: TestCase) { - run_test_with_client_ids( - case.clone(), - ["alice", "bob"], - move |[mut alice_central, mut bob_central]| { - Box::pin(async move { - let ciphersuite: Ciphersuite = case.cfg.ciphersuite.into(); - let anchors = create_intermediate_certificates( - CertificateParams { - org: "Project Zeta GmBh".to_string(), - common_name: Some("wire.com".to_string()), - domain: Some("wire.com".to_string()), - expiration: Duration::from_secs(10), - }, - ciphersuite.into(), - ); - case.cfg.per_domain_trust_anchors = vec![anchors.into()]; - let id = create_group( - case.cfg.clone(), - case.credential_type, - &mut alice_central, - &mut bob_central, - &case, - ) - .await - .unwrap(); - - let new_anchors = create_intermediate_certificates( - CertificateParams { - org: "Project Zeta GmBh".to_string(), - common_name: Some("wire.com".to_string()), - domain: Some("wire.com".to_string()), - expiration: Duration::from_secs(10), - }, - ciphersuite.into(), - ); - // try adding anchors to group - let error = alice_central - .update_trust_anchors_from_conversation(&id, vec![], vec![new_anchors.into()]) - .await - .unwrap_err(); - - assert!(matches!(error, CryptoError::DuplicateDomainName)); - let alice_anchors = alice_central - .get_conversation_unchecked(&id) - .await - .per_domain_trust_anchors(); - let bob_anchors = bob_central - .get_conversation_unchecked(&id) - .await - .per_domain_trust_anchors(); - assert_eq!(alice_anchors.len(), 1); - assert_eq!(alice_anchors, bob_anchors); - }) - }, - ) - .await; - } - - #[apply(all_cred_cipher)] - #[wasm_bindgen_test] - pub async fn should_fail_add_invalid_anchors_to_group(mut case: TestCase) { - run_test_with_client_ids( - case.clone(), - ["alice", "bob"], - move |[mut alice_central, mut bob_central]| { - Box::pin(async move { - let ciphersuite: Ciphersuite = case.cfg.ciphersuite.into(); - let anchors = create_intermediate_certificates( - CertificateParams { - org: "Project Zeta GmBh".to_string(), - common_name: Some("wire.com".to_string()), - domain: Some("wire.com".to_string()), - expiration: Duration::from_secs(10), - }, - ciphersuite.into(), - ); - case.cfg.per_domain_trust_anchors = vec![anchors.into()]; - let id = create_group( - case.cfg.clone(), - case.credential_type, - &mut alice_central, - &mut bob_central, - &case, - ) - .await - .unwrap(); - let cert = create_single_certificate( - CertificateParams { - org: "World Domination Inc".to_string(), - common_name: Some("World Domination".to_string()), - domain: None, - expiration: Duration::from_secs(10), - }, - ciphersuite.into(), - false, - ); - let ca = create_single_certificate( - CertificateParams { - org: "Project Zeta GmBh".to_string(), - common_name: Some("wire.com".to_string()), - domain: Some("wire.com".to_string()), - expiration: Duration::from_secs(10), - }, - ciphersuite.into(), - true, - ); - - // try adding anchors to group - let error = alice_central - .update_trust_anchors_from_conversation(&id, vec![], vec![vec![cert, ca].into()]) - .await - .unwrap_err(); - - assert!(matches!( - error, - CryptoError::MlsError(MlsError::MlsCryptoError(MlsCryptoError::InvalidSignature)) - )); - let alice_anchors = alice_central - .get_conversation_unchecked(&id) - .await - .per_domain_trust_anchors(); - let bob_anchors = bob_central - .get_conversation_unchecked(&id) - .await - .per_domain_trust_anchors(); - assert_eq!(alice_anchors.len(), 1); - assert_eq!(alice_anchors, bob_anchors); - }) - }, - ) - .await; - } - - #[apply(all_cred_cipher)] - #[wasm_bindgen_test] - pub async fn should_fail_add_expired_anchors_to_group(mut case: TestCase) { - run_test_with_client_ids( - case.clone(), - ["alice", "bob"], - move |[mut alice_central, mut bob_central]| { - Box::pin(async move { - let ciphersuite: Ciphersuite = case.cfg.ciphersuite.into(); - let anchors = create_intermediate_certificates( - CertificateParams { - org: "Project Zeta GmBh".to_string(), - common_name: Some("wire.com".to_string()), - domain: Some("wire.com".to_string()), - expiration: Duration::from_secs(10), - }, - ciphersuite.into(), - ); - case.cfg.per_domain_trust_anchors = vec![anchors.into()]; - let id = create_group( - case.cfg.clone(), - case.credential_type, - &mut alice_central, - &mut bob_central, - &case, - ) - .await - .unwrap(); - let new_anchors = create_intermediate_certificates( - CertificateParams { - org: "Project Zeta GmBh".to_string(), - common_name: Some("wire2.com".to_string()), - domain: Some("wire2.com".to_string()), - expiration: Duration::ZERO, - }, - ciphersuite.into(), - ); - - // try adding anchors to group - let error = alice_central - .update_trust_anchors_from_conversation(&id, vec![], vec![new_anchors.into()]) - .await - .unwrap_err(); - - assert!(matches!( - error, - CryptoError::MlsError(MlsError::MlsCryptoError(MlsCryptoError::ExpiredCertificate)) - )); - let alice_anchors = alice_central - .get_conversation_unchecked(&id) - .await - .per_domain_trust_anchors(); - let bob_anchors = bob_central - .get_conversation_unchecked(&id) - .await - .per_domain_trust_anchors(); - assert_eq!(alice_anchors.len(), 1); - assert_eq!(alice_anchors, bob_anchors); - }) - }, - ) - .await; - } - - #[apply(all_cred_cipher)] - #[wasm_bindgen_test] - pub async fn should_add_single_cert_anchors_to_group(mut case: TestCase) { - run_test_with_client_ids( - case.clone(), - ["alice", "bob"], - move |[mut alice_central, mut bob_central]| { - Box::pin(async move { - let ciphersuite: Ciphersuite = case.cfg.ciphersuite.into(); - let anchors = create_intermediate_certificates( - CertificateParams { - org: "Project Zeta GmBh".to_string(), - common_name: Some("wire.com".to_string()), - domain: Some("wire.com".to_string()), - expiration: Duration::from_secs(10), - }, - ciphersuite.into(), - ); - case.cfg.per_domain_trust_anchors = vec![anchors.into()]; - let id = create_group( - case.cfg.clone(), - case.credential_type, - &mut alice_central, - &mut bob_central, - &case, - ) - .await - .unwrap(); - let cert = create_single_certificate( - CertificateParams { - org: "Project Zeta GmBh".to_string(), - common_name: Some("wire2.com".to_string()), - domain: Some("wire2.com".to_string()), - expiration: Duration::from_secs(10), - }, - ciphersuite.into(), - false, - ); - - // try adding anchors to group - let commit_bundle = alice_central - .update_trust_anchors_from_conversation(&id, vec![], vec![cert.into()]) - .await - .unwrap(); - alice_central.commit_accepted(&id).await.unwrap(); - bob_central - .decrypt_message(&id, commit_bundle.commit.to_bytes().unwrap()) - .await - .unwrap(); - - let alice_anchors = alice_central - .get_conversation_unchecked(&id) - .await - .per_domain_trust_anchors(); - let bob_anchors = bob_central - .get_conversation_unchecked(&id) - .await - .per_domain_trust_anchors(); - assert_eq!(alice_anchors.len(), 2); - assert_eq!(alice_anchors, bob_anchors); - }) - }, - ) - .await; - } - } - - mod remove_anchors { - use super::*; - use crate::{ - mls::credential::trust_anchor::PerDomainTrustAnchor, - test_utils::{x509::*, *}, - CryptoError, - }; - - use openmls_traits::types::Ciphersuite; - use wasm_bindgen_test::*; - - wasm_bindgen_test_configure!(run_in_browser); - - #[apply(all_cred_cipher)] - #[wasm_bindgen_test] - pub async fn should_remove_anchors_from_group(mut case: TestCase) { - run_test_with_client_ids( - case.clone(), - ["alice", "bob"], - move |[mut alice_central, mut bob_central]| { - Box::pin(async move { - let ciphersuite: Ciphersuite = case.cfg.ciphersuite.into(); - let anchors = create_intermediate_certificates( - CertificateParams { - org: "Project Zeta GmBh".to_string(), - common_name: Some("wire.com".to_string()), - domain: Some("wire.com".to_string()), - expiration: Duration::from_secs(10), - }, - ciphersuite.into(), - ); - case.cfg.per_domain_trust_anchors = vec![anchors.into()]; - let id = create_group( - case.cfg.clone(), - case.credential_type, - &mut alice_central, - &mut bob_central, - &case, - ) - .await - .unwrap(); - - // remove anchor to group - let commit_bundle = alice_central - .update_trust_anchors_from_conversation(&id, vec!["wire.com".to_string()], vec![]) - .await - .unwrap(); - alice_central.commit_accepted(&id).await.unwrap(); - // bob parses the commit - bob_central - .decrypt_message(&id, &commit_bundle.commit.to_bytes().unwrap()) - .await - .unwrap(); - - // both must have the anchors in the extensions - let alice_anchors = alice_central - .get_conversation_unchecked(&id) - .await - .per_domain_trust_anchors(); - let bob_anchors = bob_central - .get_conversation_unchecked(&id) - .await - .per_domain_trust_anchors(); - assert_eq!(alice_anchors.len(), 0); - assert_eq!(alice_anchors, bob_anchors); - }) - }, - ) - .await; - } - - #[apply(all_cred_cipher)] - #[wasm_bindgen_test] - pub async fn should_remove_anchors_add_new(mut case: TestCase) { - run_test_with_client_ids( - case.clone(), - ["alice", "bob"], - move |[mut alice_central, mut bob_central]| { - Box::pin(async move { - let ciphersuite: Ciphersuite = case.cfg.ciphersuite.into(); - let anchors = create_intermediate_certificates( - CertificateParams { - org: "Project Zeta GmBh".to_string(), - common_name: Some("wire.com".to_string()), - domain: Some("wire.com".to_string()), - expiration: Duration::from_secs(10), - }, - ciphersuite.into(), - ); - case.cfg.per_domain_trust_anchors = vec![anchors.into()]; - let id = create_group( - case.cfg.clone(), - case.credential_type, - &mut alice_central, - &mut bob_central, - &case, - ) - .await - .unwrap(); - let new_anchors = create_intermediate_certificates( - CertificateParams { - org: "Project Zeta 2 GmBh".to_string(), - common_name: Some("wire2.com".to_string()), - domain: Some("wire2.com".to_string()), - expiration: Duration::from_secs(10), - }, - ciphersuite.into(), - ); - - // adding anchors to group - let commit_bundle = alice_central - .update_trust_anchors_from_conversation( - &id, - vec!["wire.com".to_string()], - vec![new_anchors.into()], - ) - .await - .unwrap(); - alice_central.commit_accepted(&id).await.unwrap(); - // bob parses the commit - bob_central - .decrypt_message(&id, &commit_bundle.commit.to_bytes().unwrap()) - .await - .unwrap(); - - // both must have the anchors in the extensions - let alice_anchors = alice_central - .get_conversation_unchecked(&id) - .await - .per_domain_trust_anchors(); - let bob_anchors = bob_central - .get_conversation_unchecked(&id) - .await - .per_domain_trust_anchors(); - assert_eq!(alice_anchors.len(), 1); - assert_eq!(alice_anchors, bob_anchors); - alice_anchors[0].validate(&alice_central.mls_backend, None).unwrap(); - assert_eq!(alice_anchors[0].domain_name, "wire2.com"); - }) - }, - ) - .await; - } - - #[apply(all_cred_cipher)] - #[wasm_bindgen_test] - pub async fn should_fail_remove_anchors_not_found(mut case: TestCase) { - run_test_with_client_ids( - case.clone(), - ["alice", "bob"], - move |[mut alice_central, mut bob_central]| { - Box::pin(async move { - let ciphersuite: Ciphersuite = case.cfg.ciphersuite.into(); - let anchors = create_intermediate_certificates( - CertificateParams { - org: "Project Zeta GmBh".to_string(), - common_name: Some("wire.com".to_string()), - domain: Some("wire.com".to_string()), - expiration: Duration::from_secs(10), - }, - ciphersuite.into(), - ); - case.cfg.per_domain_trust_anchors = vec![anchors.into()]; - let id = create_group( - case.cfg.clone(), - case.credential_type, - &mut alice_central, - &mut bob_central, - &case, - ) - .await - .unwrap(); - - // remove anchor to group - let error = alice_central - .update_trust_anchors_from_conversation(&id, vec!["wire2.com".to_string()], vec![]) - .await - .unwrap_err(); - assert!(matches!(error, CryptoError::DomainNameNotFound)); - - // both must have the anchors in the extensions - let alice_anchors = alice_central - .get_conversation_unchecked(&id) - .await - .per_domain_trust_anchors(); - let bob_anchors = bob_central - .get_conversation_unchecked(&id) - .await - .per_domain_trust_anchors(); - assert_eq!(alice_anchors.len(), 1); - assert_eq!(alice_anchors, bob_anchors); - }) - }, - ) - .await; - } - - #[apply(all_cred_cipher)] - #[wasm_bindgen_test] - pub async fn should_fail_empty_request(mut case: TestCase) { - run_test_with_client_ids( - case.clone(), - ["alice", "bob"], - move |[mut alice_central, mut bob_central]| { - Box::pin(async move { - let ciphersuite: Ciphersuite = case.cfg.ciphersuite.into(); - let anchors = create_intermediate_certificates( - CertificateParams { - org: "Project Zeta GmBh".to_string(), - common_name: Some("wire.com".to_string()), - domain: Some("wire.com".to_string()), - expiration: Duration::from_secs(10), - }, - ciphersuite.into(), - ); - case.cfg.per_domain_trust_anchors = vec![anchors.into()]; - let id = create_group( - case.cfg.clone(), - case.credential_type, - &mut alice_central, - &mut bob_central, - &case, - ) - .await - .unwrap(); - - // remove anchor to group - let error = alice_central - .update_trust_anchors_from_conversation(&id, vec![], vec![]) - .await - .unwrap_err(); - assert!(matches!(error, CryptoError::EmptyTrustAnchorUpdate)); - - // both must have the anchors in the extensions - let alice_anchors = alice_central - .get_conversation_unchecked(&id) - .await - .per_domain_trust_anchors(); - let bob_anchors = bob_central - .get_conversation_unchecked(&id) - .await - .per_domain_trust_anchors(); - assert_eq!(alice_anchors.len(), 1); - assert_eq!(alice_anchors, bob_anchors); - }) - }, - ) - .await; - } - - #[apply(all_cred_cipher)] - #[wasm_bindgen_test] - pub async fn should_replace_anchors(mut case: TestCase) { - run_test_with_client_ids( - case.clone(), - ["alice", "bob"], - move |[mut alice_central, mut bob_central]| { - Box::pin(async move { - let ciphersuite: Ciphersuite = case.cfg.ciphersuite.into(); - let anchors: PerDomainTrustAnchor = create_intermediate_certificates( - CertificateParams { - org: "Project Zeta GmBh".to_string(), - common_name: Some("wire.com".to_string()), - domain: Some("wire.com".to_string()), - expiration: Duration::from_secs(10), - }, - ciphersuite.into(), - ) - .into(); - let old_chain = anchors.intermediate_certificate_chain.clone(); - case.cfg.per_domain_trust_anchors = vec![anchors]; - let id = create_group( - case.cfg.clone(), - case.credential_type, - &mut alice_central, - &mut bob_central, - &case, - ) - .await - .unwrap(); - let new_anchors: PerDomainTrustAnchor = create_intermediate_certificates( - CertificateParams { - org: "Project Zeta GmBh".to_string(), - common_name: Some("wire.com".to_string()), - domain: Some("wire.com".to_string()), - expiration: Duration::from_secs(10), - }, - ciphersuite.into(), - ) - .into(); - let new_chain = new_anchors.intermediate_certificate_chain.clone(); - - // adding anchors to group - let commit_bundle = alice_central - .update_trust_anchors_from_conversation( - &id, - vec!["wire.com".to_string()], - vec![new_anchors], - ) - .await - .unwrap(); - alice_central.commit_accepted(&id).await.unwrap(); - // bob parses the commit - bob_central - .decrypt_message(&id, &commit_bundle.commit.to_bytes().unwrap()) - .await - .unwrap(); - - // both must have the anchors in the extensions - let alice_anchors = alice_central - .get_conversation_unchecked(&id) - .await - .per_domain_trust_anchors(); - let bob_anchors = bob_central - .get_conversation_unchecked(&id) - .await - .per_domain_trust_anchors(); - assert_eq!(alice_anchors.len(), 1); - assert_eq!(alice_anchors, bob_anchors); - alice_anchors[0].validate(&alice_central.mls_backend, None).unwrap(); - assert_eq!(alice_anchors[0].domain_name, "wire.com"); - assert_ne!(new_chain, old_chain); - }) - }, - ) - .await; - } - - #[apply(all_cred_cipher)] - #[wasm_bindgen_test] - pub async fn should_fail_re_add_same_anchors(mut case: TestCase) { - run_test_with_client_ids( - case.clone(), - ["alice", "bob"], - move |[mut alice_central, mut bob_central]| { - Box::pin(async move { - use x509_cert::der::Encode; - let ciphersuite: Ciphersuite = case.cfg.ciphersuite.into(); - let anchors = create_intermediate_certificates( - CertificateParams { - org: "Project Zeta GmBh".to_string(), - common_name: Some("wire.com".to_string()), - domain: Some("wire.com".to_string()), - expiration: Duration::from_secs(10), - }, - ciphersuite.into(), - ); - case.cfg.per_domain_trust_anchors = vec![anchors.clone().into()]; - let id = create_group( - case.cfg.clone(), - case.credential_type, - &mut alice_central, - &mut bob_central, - &case, - ) - .await - .unwrap(); - - // try adding anchors to group - let pems = anchors - .iter() - .map(|c| pem::Pem::new("CERTIFICATE", c.to_der().unwrap())) - .collect::>(); - let per_domain_trust_anchor = PerDomainTrustAnchor { - domain_name: "new_domain.com".to_string(), - intermediate_certificate_chain: pem::encode_many(&pems), - }; - let error = alice_central - .update_trust_anchors_from_conversation(&id, vec![], vec![per_domain_trust_anchor]) - .await - .unwrap_err(); - - assert!(matches!(error, CryptoError::DuplicateCertificateChain)); - let alice_anchors = alice_central - .get_conversation_unchecked(&id) - .await - .per_domain_trust_anchors(); - let bob_anchors = bob_central - .get_conversation_unchecked(&id) - .await - .per_domain_trust_anchors(); - assert_eq!(alice_anchors.len(), 1); - assert_eq!(alice_anchors, bob_anchors); - }) - }, - ) - .await; - } - } - - mod receiver_validation { - use super::*; - use crate::{ - test_utils::{x509::*, *}, - CryptoError, MlsError, - }; - - use openmls::prelude::CryptoError as MlsCryptoError; - use openmls_traits::types::Ciphersuite; - use wasm_bindgen_test::*; - - wasm_bindgen_test_configure!(run_in_browser); - - #[apply(all_cred_cipher)] - #[wasm_bindgen_test] - pub async fn should_fail_decrypting_expired(case: TestCase) { - run_test_with_client_ids( - case.clone(), - ["alice", "bob"], - move |[mut alice_central, mut bob_central]| { - Box::pin(async move { - let ciphersuite: Ciphersuite = case.cfg.ciphersuite.into(); - let id = create_group( - case.cfg.clone(), - case.credential_type, - &mut alice_central, - &mut bob_central, - &case, - ) - .await - .unwrap(); - - // adding anchors to group - let anchors = create_intermediate_certificates( - CertificateParams { - org: "Project Zeta GmBh".to_string(), - common_name: Some("wire.com".to_string()), - domain: Some("wire.com".to_string()), - expiration: Duration::ZERO, - }, - ciphersuite.into(), - ); - - // replace with manual openmls gce - let commit = alice_central - .add_per_domain_trust_anchor_unchecked(&id, anchors.into()) - .await; - alice_central.commit_accepted(&id).await.unwrap(); - // bob parses the commit - let error = bob_central - .decrypt_message(&id, &commit.to_bytes().unwrap()) - .await - .unwrap_err(); - - assert!(matches!( - error, - CryptoError::MlsError(MlsError::MlsCryptoError(MlsCryptoError::ExpiredCertificate)) - )); - - // alice should have the invalid anchor but not bob - let alice_anchors = alice_central - .get_conversation_unchecked(&id) - .await - .per_domain_trust_anchors(); - assert_eq!(alice_anchors.len(), 1); - assert!(bob_central - .get_conversation_unchecked(&id) - .await - .extensions() - .per_domain_trust_anchors() - .is_none()); - }) - }, - ) - .await; - } - - #[apply(all_cred_cipher)] - #[wasm_bindgen_test] - pub async fn should_fail_decrypting_invalid_chain(case: TestCase) { - run_test_with_client_ids( - case.clone(), - ["alice", "bob"], - move |[mut alice_central, mut bob_central]| { - Box::pin(async move { - let ciphersuite: Ciphersuite = case.cfg.ciphersuite.into(); - let id = create_group( - case.cfg.clone(), - case.credential_type, - &mut alice_central, - &mut bob_central, - &case, - ) - .await - .unwrap(); - - // adding anchors to group - let cert = create_single_certificate( - CertificateParams { - org: "World Domination Inc".to_string(), - common_name: Some("World Domination".to_string()), - domain: None, - expiration: Duration::from_secs(10), - }, - ciphersuite.into(), - false, - ); - - let ca = create_single_certificate( - CertificateParams { - org: "Project Zeta GmBh".to_string(), - common_name: Some("wire.com".to_string()), - domain: Some("wire.com".to_string()), - expiration: Duration::from_secs(10), - }, - ciphersuite.into(), - true, - ); - - // replace with manual openmls gce - let commit = alice_central - .add_per_domain_trust_anchor_unchecked(&id, vec![cert, ca].into()) - .await; - alice_central.commit_accepted(&id).await.unwrap(); - // bob parses the commit - let message = &commit.to_bytes().unwrap(); - let error = bob_central.decrypt_message(&id, message).await.unwrap_err(); - - assert!(matches!( - error, - CryptoError::MlsError(MlsError::MlsCryptoError(MlsCryptoError::InvalidSignature)) - )); - - // alice should have the invalid anchor but not bob - let alice_anchors = alice_central - .get_conversation_unchecked(&id) - .await - .per_domain_trust_anchors(); - assert_eq!(alice_anchors.len(), 1); - assert!(bob_central - .get_conversation_unchecked(&id) - .await - .extensions() - .per_domain_trust_anchors() - .is_none()); - }) - }, - ) - .await; - } - } - - async fn create_group( - cfg: MlsConversationConfiguration, - credential_type: MlsCredentialType, - alice_central: &mut MlsCentral, - bob_central: &mut MlsCentral, - test_case: &TestCase, - ) -> CryptoResult { - let id = conversation_id(); - let custom_cfg = cfg.custom.clone(); - alice_central.new_conversation(&id, credential_type, cfg).await?; - assert_eq!(alice_central.get_conversation_unchecked(&id).await.id, id); - - let bob = bob_central.rand_key_package(test_case).await; - let MlsConversationCreationMessage { welcome, .. } = - alice_central.add_members_to_conversation(&id, vec![bob]).await?; - assert_eq!(alice_central.get_conversation_unchecked(&id).await.members().len(), 1); - alice_central.commit_accepted(&id).await?; - bob_central - .try_join_from_welcome(&id, welcome.into(), custom_cfg, vec![alice_central]) - .await?; - Ok(id) - } -} diff --git a/crypto/src/mls/external_commit.rs b/crypto/src/mls/external_commit.rs index 748baf6165..c475a1e38f 100644 --- a/crypto/src/mls/external_commit.rs +++ b/crypto/src/mls/external_commit.rs @@ -107,7 +107,7 @@ impl MlsCentral { &cb.signature_key, None, group_info, - &configuration.as_openmls_default_configuration(&self.mls_backend)?, + &configuration.as_openmls_default_configuration()?, &[], cb.to_mls_credential_with_key(), ) @@ -787,7 +787,7 @@ pub mod tests { let capabilities = group.group.group_context_extensions().required_capabilities().unwrap(); // see https://www.rfc-editor.org/rfc/rfc9420.html#section-11.1 - assert_eq!(capabilities.extension_types(), &[ExtensionType::PerDomainTrustAnchor]); + assert!(capabilities.extension_types().is_empty()); assert!(capabilities.proposal_types().is_empty()); assert_eq!( capabilities.credential_types(), diff --git a/crypto/src/test_utils/central.rs b/crypto/src/test_utils/central.rs index b1e6867d06..b1a87be4e6 100644 --- a/crypto/src/test_utils/central.rs +++ b/crypto/src/test_utils/central.rs @@ -15,7 +15,7 @@ // along with this program. If not, see http://www.gnu.org/licenses/. use crate::{ - mls::credential::{ext::CredentialExt, trust_anchor::PerDomainTrustAnchor, CredentialBundle}, + mls::credential::{ext::CredentialExt, CredentialBundle}, prelude::{ CertificateBundle, Client, ClientId, ConversationId, CryptoError, CryptoResult, MlsCentral, MlsCiphersuite, MlsConversation, MlsConversationConfiguration, MlsConversationDecryptMessage, MlsConversationInitBundle, @@ -25,8 +25,7 @@ use crate::{ }; use openmls::prelude::{ group_info::VerifiableGroupInfo, Capabilities, Credential, CredentialWithKey, CryptoConfig, HpkePublicKey, - KeyPackage, KeyPackageIn, LeafNodeIndex, Lifetime, MlsMessageIn, MlsMessageOut, QueuedProposal, SignaturePublicKey, - StagedCommit, + KeyPackage, KeyPackageIn, LeafNodeIndex, Lifetime, MlsMessageIn, QueuedProposal, SignaturePublicKey, StagedCommit, }; use openmls_traits::{types::SignatureScheme, OpenMlsCryptoProvider}; use tls_codec::{Deserialize, Serialize}; @@ -77,33 +76,6 @@ impl MlsCentral { .unwrap() } - pub async fn new_invalid_keypackage(&self, case: &TestCase) -> KeyPackage { - let cb = self - .find_most_recent_credential_bundle(case.signature_scheme(), case.credential_type) - .await - .unwrap(); - let capabilities = Capabilities { - extensions: vec![], - ..MlsConversationConfiguration::default_leaf_capabilities() - }; - KeyPackage::builder() - .leaf_node_capabilities(capabilities) - .build( - CryptoConfig { - ciphersuite: case.ciphersuite().into(), - version: openmls::versions::ProtocolVersion::default(), - }, - &self.mls_backend, - &cb.signature_key, - CredentialWithKey { - credential: cb.credential.clone(), - signature_key: cb.signature_key.public().into(), - }, - ) - .await - .unwrap() - } - pub async fn count_key_package(&self, cs: MlsCiphersuite, ct: Option) -> usize { self.mls_backend .key_store() @@ -564,20 +536,6 @@ impl MlsCentral { assert!(!identity.thumbprint.is_empty()); } } - - pub async fn add_per_domain_trust_anchor_unchecked( - &mut self, - id: &ConversationId, - trust_anchor: PerDomainTrustAnchor, - ) -> MlsMessageOut { - self.get_conversation(id) - .await - .unwrap() - .write() - .await - .add_per_domain_trust_anchor_unchecked(trust_anchor, self.mls_client().unwrap(), &self.mls_backend) - .await - } } impl MlsConversation { @@ -595,38 +553,6 @@ impl MlsConversation { pub fn extensions(&self) -> &openmls::prelude::Extensions { self.group.export_group_context().extensions() } - - pub fn per_domain_trust_anchors(&self) -> Vec { - self.extensions() - .per_domain_trust_anchors() - .unwrap() - .iter() - .map(|a| PerDomainTrustAnchor::try_from(a).unwrap()) - .collect() - } - - pub async fn add_per_domain_trust_anchor_unchecked( - &mut self, - trust_anchor: PerDomainTrustAnchor, - client: &Client, - backend: &MlsCryptoProvider, - ) -> MlsMessageOut { - let context = self.group.export_group_context(); - let mut extensions = context.extensions().clone(); - let mls_trust_anchor = trust_anchor.into_mls_unchecked(); - extensions.add_or_replace(openmls::prelude::Extension::PerDomainTrustAnchor(vec![ - mls_trust_anchor, - ])); - let cs = self.ciphersuite(); - let ct = self.own_credential_type().unwrap(); - let signer = &client - .find_most_recent_credential_bundle(cs.signature_algorithm(), ct) - .unwrap() - .signature_key; - let (commit, _, _) = self.group.update_extensions(backend, signer, extensions).await.unwrap(); - self.persist_group_when_changed(backend, false).await.unwrap(); - commit - } } impl Client { diff --git a/crypto/src/test_utils/x509.rs b/crypto/src/test_utils/x509.rs index 5907a91def..7638f86c68 100644 --- a/crypto/src/test_utils/x509.rs +++ b/crypto/src/test_utils/x509.rs @@ -2,12 +2,7 @@ use std::time::Duration; use openmls_traits::types::SignatureScheme; use time::OffsetDateTime; -use x509_cert::{ - der::{Decode, Encode}, - Certificate, PkiPath, -}; - -use crate::mls::credential::trust_anchor::{extract_domain_names, PerDomainTrustAnchor}; +use x509_cert::{der::Decode, Certificate, PkiPath}; /// Params for generating the Certificate chain #[derive(Debug, Clone)] @@ -18,47 +13,6 @@ pub struct CertificateParams { pub expiration: Duration, } -impl PerDomainTrustAnchor { - pub fn into_mls_unchecked(self) -> openmls::extensions::PerDomainTrustAnchor { - let certificate_chain = pem::parse_many(&self.intermediate_certificate_chain) - .unwrap() - .into_iter() - .map(|p| p.into_contents()) - .collect(); - openmls::extensions::PerDomainTrustAnchor::new( - self.domain_name.into(), - openmls::prelude::CredentialType::X509, - certificate_chain, - ) - .unwrap() - } -} - -impl From for PerDomainTrustAnchor { - fn from(chain: PkiPath) -> Self { - let domains = extract_domain_names(&chain[0]).unwrap_or_default(); - let pems = chain - .iter() - .map(|c| pem::Pem::new("CERTIFICATE", c.to_der().unwrap())) - .collect::>(); - Self { - domain_name: domains.first().cloned().unwrap_or_default(), - intermediate_certificate_chain: pem::encode_many(&pems), - } - } -} - -impl From for PerDomainTrustAnchor { - fn from(cert: Certificate) -> Self { - let mut domains = extract_domain_names(&cert).unwrap_or_default(); - let pem = pem::Pem::new("CERTIFICATE", cert.to_der().unwrap()); - Self { - domain_name: domains.remove(0), - intermediate_certificate_chain: pem::encode(&pem), - } - } -} - /// Create a certificate chain with a CA and a Leaf for usage with the certificate trust anchors /// extension pub fn create_intermediate_certificates(cert_params: CertificateParams, signature_scheme: SignatureScheme) -> PkiPath {