use mls_crypto_provider::MlsCryptoProvider;
use openmls::prelude::{
Capabilities, Credential, CredentialType, ExternalSender, OpenMlsSignaturePublicKey, ProtocolVersion,
RequiredCapabilitiesExtension, SenderRatchetConfiguration, WireFormatPolicy, PURE_CIPHERTEXT_WIRE_FORMAT_POLICY,
PURE_PLAINTEXT_WIRE_FORMAT_POLICY,
};
use openmls_traits::crypto::OpenMlsCrypto;
use openmls_traits::types::{Ciphersuite, SignatureScheme};
use openmls_traits::OpenMlsCryptoProvider;
use serde::{Deserialize, Serialize};
use wire_e2e_identity::prelude::parse_json_jwk;
use crate::context::CentralContext;
use crate::prelude::{CryptoResult, E2eIdentityError, MlsCiphersuite};
use crate::MlsError;
pub(crate) const MAX_PAST_EPOCHS: usize = 3;
pub(crate) const OUT_OF_ORDER_TOLERANCE: u32 = 2;
pub(crate) const MAXIMUM_FORWARD_DISTANCE: u32 = 1000;
impl CentralContext {
pub async fn set_raw_external_senders(
&self,
cfg: &mut MlsConversationConfiguration,
external_senders: Vec<Vec<u8>>,
) -> CryptoResult<()> {
let mls_provider = self.mls_provider().await?;
cfg.external_senders = external_senders
.into_iter()
.map(|key| {
MlsConversationConfiguration::parse_external_sender(&key).or_else(|_| {
MlsConversationConfiguration::legacy_external_sender(
key,
cfg.ciphersuite.signature_algorithm(),
&mls_provider,
)
})
})
.collect::<CryptoResult<_>>()?;
Ok(())
}
}
#[derive(Debug, Clone, Default)]
pub struct MlsConversationConfiguration {
pub ciphersuite: MlsCiphersuite,
pub external_senders: Vec<ExternalSender>,
pub custom: MlsCustomConfiguration,
}
impl MlsConversationConfiguration {
const WIRE_SERVER_IDENTITY: &'static str = "wire-server";
const PADDING_SIZE: usize = 128;
pub(crate) const DEFAULT_PROTOCOL_VERSION: ProtocolVersion = ProtocolVersion::Mls10;
pub(crate) const DEFAULT_SUPPORTED_CREDENTIALS: &'static [CredentialType] =
&[CredentialType::Basic, CredentialType::X509];
pub(crate) const DEFAULT_SUPPORTED_CIPHERSUITES: &'static [Ciphersuite] = &[
Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519,
Ciphersuite::MLS_128_DHKEMP256_AES128GCM_SHA256_P256,
Ciphersuite::MLS_128_DHKEMX25519_CHACHA20POLY1305_SHA256_Ed25519,
Ciphersuite::MLS_256_DHKEMP384_AES256GCM_SHA384_P384,
Ciphersuite::MLS_256_DHKEMP521_AES256GCM_SHA512_P521,
];
const NUMBER_RESUMPTION_PSK: usize = 1;
#[inline(always)]
pub fn as_openmls_default_configuration(&self) -> CryptoResult<openmls::group::MlsGroupConfig> {
let crypto_config = openmls::prelude::CryptoConfig {
version: Self::DEFAULT_PROTOCOL_VERSION,
ciphersuite: self.ciphersuite.into(),
};
Ok(openmls::group::MlsGroupConfig::builder()
.wire_format_policy(self.custom.wire_policy.into())
.max_past_epochs(MAX_PAST_EPOCHS)
.padding_size(Self::PADDING_SIZE)
.number_of_resumption_psks(Self::NUMBER_RESUMPTION_PSK)
.leaf_capabilities(Self::default_leaf_capabilities())
.required_capabilities(self.default_required_capabilities())
.sender_ratchet_configuration(SenderRatchetConfiguration::new(
self.custom.out_of_order_tolerance,
self.custom.maximum_forward_distance,
))
.use_ratchet_tree_extension(true)
.external_senders(self.external_senders.clone())
.crypto_config(crypto_config)
.build())
}
pub fn default_leaf_capabilities() -> Capabilities {
Capabilities::new(
Some(&[Self::DEFAULT_PROTOCOL_VERSION]),
Some(Self::DEFAULT_SUPPORTED_CIPHERSUITES),
Some(&[]),
Some(&[]),
Some(Self::DEFAULT_SUPPORTED_CREDENTIALS),
)
}
fn default_required_capabilities(&self) -> RequiredCapabilitiesExtension {
RequiredCapabilitiesExtension::new(&[], &[], Self::DEFAULT_SUPPORTED_CREDENTIALS)
}
fn parse_external_sender(jwk: &[u8]) -> CryptoResult<ExternalSender> {
let pk = parse_json_jwk(jwk)
.map_err(wire_e2e_identity::prelude::E2eIdentityError::from)
.map_err(E2eIdentityError::from)?;
Ok(ExternalSender::new(
pk.into(),
Credential::new_basic(Self::WIRE_SERVER_IDENTITY.into()),
))
}
fn legacy_external_sender(
key: Vec<u8>,
signature_scheme: SignatureScheme,
backend: &MlsCryptoProvider,
) -> CryptoResult<ExternalSender> {
backend
.crypto()
.validate_signature_key(signature_scheme, &key[..])
.map_err(MlsError::from)?;
let key = OpenMlsSignaturePublicKey::new(key.into(), signature_scheme).map_err(MlsError::from)?;
Ok(ExternalSender::new(
key.into(),
Credential::new_basic(Self::WIRE_SERVER_IDENTITY.into()),
))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MlsCustomConfiguration {
pub key_rotation_span: Option<std::time::Duration>,
pub wire_policy: MlsWirePolicy,
pub out_of_order_tolerance: u32,
pub maximum_forward_distance: u32,
}
impl Default for MlsCustomConfiguration {
fn default() -> Self {
Self {
wire_policy: MlsWirePolicy::Plaintext,
key_rotation_span: Default::default(),
out_of_order_tolerance: OUT_OF_ORDER_TOLERANCE,
maximum_forward_distance: MAXIMUM_FORWARD_DISTANCE,
}
}
}
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
#[repr(u8)]
pub enum MlsWirePolicy {
#[default]
Plaintext = 1,
Ciphertext = 2,
}
impl From<MlsWirePolicy> for WireFormatPolicy {
fn from(policy: MlsWirePolicy) -> Self {
match policy {
MlsWirePolicy::Ciphertext => PURE_CIPHERTEXT_WIRE_FORMAT_POLICY,
MlsWirePolicy::Plaintext => PURE_PLAINTEXT_WIRE_FORMAT_POLICY,
}
}
}
#[cfg(test)]
mod tests {
use openmls::prelude::ProtocolVersion;
use openmls_traits::{
crypto::OpenMlsCrypto,
types::{SignatureScheme, VerifiableCiphersuite},
OpenMlsCryptoProvider,
};
use wasm_bindgen_test::*;
use wire_e2e_identity::prelude::JwsAlgorithm;
use crate::{prelude::MlsConversationConfiguration, test_utils::*};
wasm_bindgen_test_configure!(run_in_browser);
#[cfg_attr(not(target_family = "wasm"), async_std::test)]
#[wasm_bindgen_test]
pub async fn group_should_have_required_capabilities() {
let case = TestCase::default();
run_test_with_client_ids(case.clone(), ["alice"], move |[cc]| {
Box::pin(async move {
let id = conversation_id();
cc.context
.new_conversation(&id, case.credential_type, case.cfg.clone())
.await
.unwrap();
let conv = cc.context.get_conversation(&id).await.unwrap();
let group = conv.read().await;
let capabilities = group.group.group_context_extensions().required_capabilities().unwrap();
assert!(capabilities.extension_types().is_empty());
assert!(capabilities.proposal_types().is_empty());
assert_eq!(
capabilities.credential_types(),
MlsConversationConfiguration::DEFAULT_SUPPORTED_CREDENTIALS
);
})
})
.await
}
#[apply(all_cred_cipher)]
#[wasm_bindgen_test]
pub async fn creator_leaf_node_should_have_default_capabilities(case: TestCase) {
run_test_with_client_ids(case.clone(), ["alice"], move |[cc]| {
Box::pin(async move {
let id = conversation_id();
cc.context
.new_conversation(&id, case.credential_type, case.cfg.clone())
.await
.unwrap();
let conv = cc.context.get_conversation(&id).await.unwrap();
let group = conv.read().await;
let creator_capabilities = group.group.own_leaf().unwrap().capabilities();
assert_eq!(creator_capabilities.versions(), &[ProtocolVersion::Mls10]);
assert_eq!(
creator_capabilities.ciphersuites().to_vec(),
MlsConversationConfiguration::DEFAULT_SUPPORTED_CIPHERSUITES
.iter()
.map(|c| VerifiableCiphersuite::from(*c))
.collect::<Vec<_>>()
);
assert!(creator_capabilities.proposals().is_empty());
assert!(creator_capabilities.extensions().is_empty(),);
assert_eq!(
creator_capabilities.credentials(),
MlsConversationConfiguration::DEFAULT_SUPPORTED_CREDENTIALS
);
})
})
.await
}
#[apply(all_cred_cipher)]
#[wasm_bindgen_test]
pub async fn should_support_raw_external_sender(case: TestCase) {
run_test_with_client_ids(case.clone(), ["alice"], move |[cc]| {
Box::pin(async move {
let (_sk, pk) = cc
.context
.mls_provider()
.await
.unwrap()
.crypto()
.signature_key_gen(case.signature_scheme())
.unwrap();
assert!(cc
.context
.set_raw_external_senders(&mut case.cfg.clone(), vec![pk])
.await
.is_ok());
})
})
.await
}
#[apply(all_cred_cipher)]
#[wasm_bindgen_test]
pub async fn should_support_jwk_external_sender(case: TestCase) {
run_test_with_client_ids(case.clone(), ["alice"], move |[cc]| {
Box::pin(async move {
let sc = case.signature_scheme();
let alg = match sc {
SignatureScheme::ED25519 => JwsAlgorithm::Ed25519,
SignatureScheme::ECDSA_SECP256R1_SHA256 => JwsAlgorithm::P256,
SignatureScheme::ECDSA_SECP384R1_SHA384 => JwsAlgorithm::P384,
SignatureScheme::ECDSA_SECP521R1_SHA512 => JwsAlgorithm::P521,
SignatureScheme::ED448 => unreachable!(),
};
let jwk = wire_e2e_identity::prelude::generate_jwk(alg);
cc.context
.set_raw_external_senders(&mut case.cfg.clone(), vec![jwk])
.await
.unwrap();
})
})
.await;
}
}