Cursor: Apply local changes for cloud agent
This commit is contained in:
@@ -221,11 +221,11 @@ mod tests {
|
||||
fn roundtrip_chat() {
|
||||
let body = b"hello";
|
||||
let encoded = serialize_chat(body, None);
|
||||
let (t, msg) = parse(&encoded).unwrap();
|
||||
let (t, msg) = parse(&encoded).expect("serialize_chat output is valid");
|
||||
assert_eq!(t, MessageType::Chat);
|
||||
match &msg {
|
||||
AppMessage::Chat { message_id: _, body: b } => assert_eq!(b.as_slice(), body),
|
||||
_ => panic!("expected Chat"),
|
||||
assert!(matches!(&msg, AppMessage::Chat { .. }), "expected Chat, got {:?}", msg);
|
||||
if let AppMessage::Chat { body: b, .. } = &msg {
|
||||
assert_eq!(b.as_slice(), body);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,25 +234,23 @@ mod tests {
|
||||
let ref_id = [1u8; 16];
|
||||
let body = b"reply text";
|
||||
let encoded = serialize_reply(ref_id, body);
|
||||
let (t, msg) = parse(&encoded).unwrap();
|
||||
let (t, msg) = parse(&encoded).expect("serialize_reply output is valid");
|
||||
assert_eq!(t, MessageType::Reply);
|
||||
match &msg {
|
||||
AppMessage::Reply { ref_msg_id, body: b } => {
|
||||
assert_eq!(ref_msg_id, &ref_id);
|
||||
assert_eq!(b.as_slice(), body);
|
||||
}
|
||||
_ => panic!("expected Reply"),
|
||||
assert!(matches!(&msg, AppMessage::Reply { .. }), "expected Reply, got {:?}", msg);
|
||||
if let AppMessage::Reply { ref_msg_id, body: b } = &msg {
|
||||
assert_eq!(ref_msg_id, &ref_id);
|
||||
assert_eq!(b.as_slice(), body);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn roundtrip_typing() {
|
||||
let encoded = serialize_typing(1);
|
||||
let (t, msg) = parse(&encoded).unwrap();
|
||||
let (t, msg) = parse(&encoded).expect("serialize_typing output is valid");
|
||||
assert_eq!(t, MessageType::Typing);
|
||||
match &msg {
|
||||
AppMessage::Typing { active } => assert_eq!(*active, 1),
|
||||
_ => panic!("expected Typing"),
|
||||
assert!(matches!(&msg, AppMessage::Typing { .. }), "expected Typing, got {:?}", msg);
|
||||
if let AppMessage::Typing { active } = &msg {
|
||||
assert_eq!(*active, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
//!
|
||||
//! # Design
|
||||
//!
|
||||
//! [`GroupMember`] wraps an openmls [`MlsGroup`] plus the per-client
|
||||
//! [`StoreCrypto`] backend. The backend is **persistent** — it holds the
|
||||
//! in-memory key store that maps init-key references to HPKE private keys.
|
||||
//! [`GroupMember`] wraps an openmls [`MlsGroup`] plus a per-client crypto
|
||||
//! backend ([`StoreCrypto`] or [`HybridCryptoProvider`] for M7). The backend
|
||||
//! is **persistent** — it holds the key store that maps init-key references
|
||||
//! to HPKE private keys (classical or hybrid).
|
||||
//! openmls's `new_from_welcome` reads those private keys from the key store to
|
||||
//! decrypt the Welcome, so the same backend instance must be used from
|
||||
//! `generate_key_package` through `join_group`.
|
||||
@@ -37,6 +38,7 @@ use openmls_traits::OpenMlsCryptoProvider;
|
||||
|
||||
use crate::{
|
||||
error::CoreError,
|
||||
hybrid_crypto::HybridCryptoProvider,
|
||||
identity::IdentityKeypair,
|
||||
keystore::{DiskKeyStore, StoreCrypto},
|
||||
};
|
||||
@@ -49,6 +51,9 @@ const CIPHERSUITE: Ciphersuite = Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA2
|
||||
|
||||
/// Per-client MLS state: identity keypair, crypto backend, and optional group.
|
||||
///
|
||||
/// Generic over the crypto provider `P`: [`StoreCrypto`] (default, classical)
|
||||
/// or [`HybridCryptoProvider`] (M7, post-quantum hybrid KEM).
|
||||
///
|
||||
/// # Lifecycle
|
||||
///
|
||||
/// ```text
|
||||
@@ -60,10 +65,10 @@ const CIPHERSUITE: Ciphersuite = Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA2
|
||||
/// ├─ send_message(msg) → encrypt application data
|
||||
/// └─ receive_message(b) → decrypt; returns Some(plaintext) or None
|
||||
/// ```
|
||||
pub struct GroupMember {
|
||||
/// Persistent crypto backend. Holds the in-memory key store with HPKE
|
||||
pub struct GroupMember<P: OpenMlsCryptoProvider = StoreCrypto> {
|
||||
/// Crypto backend (classical or hybrid). Holds the key store with HPKE
|
||||
/// private keys created during `generate_key_package`.
|
||||
backend: StoreCrypto,
|
||||
backend: P,
|
||||
/// Long-term Ed25519 identity keypair. Also used as the MLS `Signer`.
|
||||
identity: Arc<IdentityKeypair>,
|
||||
/// Active MLS group, if any.
|
||||
@@ -72,8 +77,8 @@ pub struct GroupMember {
|
||||
config: MlsGroupConfig,
|
||||
}
|
||||
|
||||
impl GroupMember {
|
||||
/// Create a new `GroupMember` with a fresh crypto backend.
|
||||
impl GroupMember<StoreCrypto> {
|
||||
/// Create a new `GroupMember` with a fresh crypto backend (classical X25519).
|
||||
pub fn new(identity: Arc<IdentityKeypair>) -> Self {
|
||||
Self::new_with_state(identity, DiskKeyStore::ephemeral(), None)
|
||||
}
|
||||
@@ -105,6 +110,41 @@ impl GroupMember {
|
||||
config,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GroupMember<HybridCryptoProvider> {
|
||||
/// Create a `GroupMember` that uses post-quantum hybrid KEM (X25519 + ML-KEM-768) for HPKE.
|
||||
///
|
||||
/// All members of a group must use the same provider type: if the creator uses
|
||||
/// `new_with_hybrid`, KeyPackages will have hybrid init keys and joiners must
|
||||
/// also use `new_with_hybrid` to decrypt the Welcome.
|
||||
pub fn new_with_hybrid(
|
||||
identity: Arc<IdentityKeypair>,
|
||||
key_store: DiskKeyStore,
|
||||
) -> Self {
|
||||
Self::new_with_state_hybrid(identity, key_store, None)
|
||||
}
|
||||
|
||||
/// Create a PQ `GroupMember` from persisted state (identity, key store, optional group).
|
||||
pub fn new_with_state_hybrid(
|
||||
identity: Arc<IdentityKeypair>,
|
||||
key_store: DiskKeyStore,
|
||||
group: Option<MlsGroup>,
|
||||
) -> Self {
|
||||
let config = MlsGroupConfig::builder()
|
||||
.use_ratchet_tree_extension(true)
|
||||
.build();
|
||||
|
||||
Self {
|
||||
backend: HybridCryptoProvider::new(key_store),
|
||||
identity,
|
||||
group,
|
||||
config,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: OpenMlsCryptoProvider> GroupMember<P> {
|
||||
|
||||
// ── KeyPackage ────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -414,7 +454,7 @@ impl GroupMember {
|
||||
}
|
||||
|
||||
/// Return a reference to the underlying crypto backend.
|
||||
pub fn backend(&self) -> &StoreCrypto {
|
||||
pub fn backend(&self) -> &P {
|
||||
&self.backend
|
||||
}
|
||||
|
||||
@@ -498,6 +538,48 @@ mod tests {
|
||||
assert_eq!(pt_creator, b"hello back");
|
||||
}
|
||||
|
||||
/// M7: Full two-party MLS round-trip with post-quantum hybrid KEM (HybridCryptoProvider).
|
||||
#[test]
|
||||
fn two_party_mls_round_trip_hybrid() {
|
||||
let creator_id = Arc::new(IdentityKeypair::generate());
|
||||
let joiner_id = Arc::new(IdentityKeypair::generate());
|
||||
let key_store_creator = DiskKeyStore::ephemeral();
|
||||
let key_store_joiner = DiskKeyStore::ephemeral();
|
||||
|
||||
let mut creator =
|
||||
GroupMember::<HybridCryptoProvider>::new_with_hybrid(Arc::clone(&creator_id), key_store_creator);
|
||||
let mut joiner =
|
||||
GroupMember::<HybridCryptoProvider>::new_with_hybrid(Arc::clone(&joiner_id), key_store_joiner);
|
||||
|
||||
let joiner_kp = joiner
|
||||
.generate_key_package()
|
||||
.expect("joiner KeyPackage (hybrid)");
|
||||
|
||||
creator
|
||||
.create_group(b"test-group-m7-hybrid")
|
||||
.expect("creator create group");
|
||||
|
||||
let (_, welcome) = creator
|
||||
.add_member(&joiner_kp)
|
||||
.expect("creator add joiner");
|
||||
|
||||
joiner.join_group(&welcome).expect("joiner join group");
|
||||
|
||||
let ct_creator = creator.send_message(b"hello pq").expect("creator send");
|
||||
let pt_joiner = joiner
|
||||
.receive_message(&ct_creator)
|
||||
.expect("joiner recv")
|
||||
.expect("application message");
|
||||
assert_eq!(pt_joiner, b"hello pq");
|
||||
|
||||
let ct_joiner = joiner.send_message(b"hello back pq").expect("joiner send");
|
||||
let pt_creator = creator
|
||||
.receive_message(&ct_joiner)
|
||||
.expect("creator recv")
|
||||
.expect("application message");
|
||||
assert_eq!(pt_creator, b"hello back pq");
|
||||
}
|
||||
|
||||
/// `group_id()` returns None before create_group, Some afterwards.
|
||||
#[test]
|
||||
fn group_id_lifecycle() {
|
||||
|
||||
@@ -38,4 +38,5 @@ pub use hybrid_kem::{
|
||||
pub use hybrid_crypto::{HybridCrypto, HybridCryptoProvider};
|
||||
pub use identity::IdentityKeypair;
|
||||
pub use keypackage::{generate_key_package, validate_keypackage_ciphersuite};
|
||||
pub use keystore::DiskKeyStore;
|
||||
pub use keystore::{DiskKeyStore, StoreCrypto};
|
||||
pub use openmls::prelude::MlsGroup;
|
||||
|
||||
Reference in New Issue
Block a user