From 90a29183d381acf99438b51e467ffae99a74edab Mon Sep 17 00:00:00 2001 From: Franziskus Kiefer Date: Fri, 8 Dec 2023 10:40:09 +0100 Subject: [PATCH 1/2] PoC: Protected (Immutable) Metadata Extension --- openmls/src/extensions/codec.rs | 7 +- openmls/src/extensions/mod.rs | 35 ++ openmls/src/extensions/protected_metadata.rs | 369 +++++++++++++++++++ 3 files changed, 410 insertions(+), 1 deletion(-) create mode 100644 openmls/src/extensions/protected_metadata.rs diff --git a/openmls/src/extensions/codec.rs b/openmls/src/extensions/codec.rs index 936ee63fa2..487083b104 100644 --- a/openmls/src/extensions/codec.rs +++ b/openmls/src/extensions/codec.rs @@ -8,7 +8,7 @@ use crate::extensions::{ UnknownExtension, }; -use super::last_resort::LastResortExtension; +use super::{last_resort::LastResortExtension, protected_metadata::ProtectedMetadata}; fn vlbytes_len_len(length: usize) -> usize { if length < 0x40 { @@ -37,6 +37,7 @@ impl Size for Extension { Extension::ExternalPub(e) => e.tls_serialized_len(), Extension::ExternalSenders(e) => e.tls_serialized_len(), Extension::LastResort(e) => e.tls_serialized_len(), + Extension::ProtectedMetadata(e) => e.tls_serialized_len(), Extension::Unknown(_, e) => e.0.len(), }; @@ -69,6 +70,7 @@ impl Serialize for Extension { Extension::ExternalPub(e) => e.tls_serialize(&mut extension_data), Extension::ExternalSenders(e) => e.tls_serialize(&mut extension_data), Extension::LastResort(e) => e.tls_serialize(&mut extension_data), + Extension::ProtectedMetadata(e) => e.tls_serialize(&mut extension_data), Extension::Unknown(_, e) => extension_data .write_all(e.0.as_slice()) .map(|_| e.0.len()) @@ -118,6 +120,9 @@ impl Deserialize for Extension { ExtensionType::LastResort => { Extension::LastResort(LastResortExtension::tls_deserialize(&mut extension_data)?) } + ExtensionType::ProtectedMetadata => Extension::ProtectedMetadata( + ProtectedMetadata::tls_deserialize(&mut extension_data)?, + ), ExtensionType::Unknown(unknown) => { Extension::Unknown(unknown, UnknownExtension(extension_data.to_vec())) } diff --git a/openmls/src/extensions/mod.rs b/openmls/src/extensions/mod.rs index 46d3932cc2..02bfcec8e0 100644 --- a/openmls/src/extensions/mod.rs +++ b/openmls/src/extensions/mod.rs @@ -32,6 +32,7 @@ mod codec; mod external_pub_extension; mod external_sender_extension; mod last_resort; +mod protected_metadata; mod ratchet_tree_extension; mod required_capabilities; use errors::*; @@ -49,6 +50,8 @@ pub use last_resort::LastResortExtension; pub use ratchet_tree_extension::RatchetTreeExtension; pub use required_capabilities::RequiredCapabilitiesExtension; +use self::protected_metadata::ProtectedMetadata; + #[cfg(test)] mod test_extensions; @@ -93,6 +96,10 @@ pub enum ExtensionType { /// scenario. LastResort, + /// Protected metadata extension for policies of the group. GroupContext + /// extension + ProtectedMetadata, + /// A currently unknown extension type. Unknown(u16), } @@ -132,6 +139,7 @@ impl From for ExtensionType { 4 => ExtensionType::ExternalPub, 5 => ExtensionType::ExternalSenders, 10 => ExtensionType::LastResort, + 11 => ExtensionType::ProtectedMetadata, unknown => ExtensionType::Unknown(unknown), } } @@ -146,6 +154,7 @@ impl From for u16 { ExtensionType::ExternalPub => 4, ExtensionType::ExternalSenders => 5, ExtensionType::LastResort => 10, + ExtensionType::ProtectedMetadata => 11, ExtensionType::Unknown(unknown) => unknown, } } @@ -162,6 +171,7 @@ impl ExtensionType { | ExtensionType::ExternalPub | ExtensionType::ExternalSenders | ExtensionType::LastResort + | ExtensionType::ProtectedMetadata ) } } @@ -200,6 +210,9 @@ pub enum Extension { /// A [`LastResortExtension`] LastResort(LastResortExtension), + /// A [`ProtectedMetadata`] extension + ProtectedMetadata(ProtectedMetadata), + /// A currently unknown extension. Unknown(u16, UnknownExtension), } @@ -378,6 +391,15 @@ impl Extensions { _ => None, }) } + + /// Get a reference to the [`ProtectedMetadata`] if there is any. + pub fn protected_metadata(&self) -> Option<&ProtectedMetadata> { + self.find_by_type(ExtensionType::ExternalSenders) + .and_then(|e| match e { + Extension::ProtectedMetadata(e) => Some(e), + _ => None, + }) + } } impl Extension { @@ -445,6 +467,18 @@ impl Extension { } } + /// Get a reference to this extension as [`ProtectedMetadata`]. + /// Returns an [`ExtensionError::InvalidExtensionType`] error if called on + /// an [`Extension`] that's not a [`ProtectedMetadata`] extension. + pub fn as_protected_metadata_extension(&self) -> Result<&ProtectedMetadata, ExtensionError> { + match self { + Self::ProtectedMetadata(e) => Ok(e), + _ => Err(ExtensionError::InvalidExtensionType( + "This is not an ProtectedMetadata".into(), + )), + } + } + /// Returns the [`ExtensionType`] #[inline] pub const fn extension_type(&self) -> ExtensionType { @@ -455,6 +489,7 @@ impl Extension { Extension::ExternalPub(_) => ExtensionType::ExternalPub, Extension::ExternalSenders(_) => ExtensionType::ExternalSenders, Extension::LastResort(_) => ExtensionType::LastResort, + Extension::ProtectedMetadata(_) => ExtensionType::ProtectedMetadata, Extension::Unknown(kind, _) => ExtensionType::Unknown(*kind), } } diff --git a/openmls/src/extensions/protected_metadata.rs b/openmls/src/extensions/protected_metadata.rs new file mode 100644 index 0000000000..a9aa8423ba --- /dev/null +++ b/openmls/src/extensions/protected_metadata.rs @@ -0,0 +1,369 @@ +use std::time::{SystemTime, UNIX_EPOCH}; + +use openmls_traits::signatures::Signer; +use tls_codec::{Serialize as TlsSerializeTrait, TlsDeserialize, TlsSerialize, TlsSize}; + +use super::{Deserialize, Serialize}; +use crate::{ + ciphersuite::{ + signable::{Signable, SignatureError, SignedStruct, Verifiable, VerifiedStruct}, + signature::Signature, + }, + credentials::Credential, +}; + +/// # Protected Metadata +/// +/// ```c +/// struct { +/// opaque signer_application_id; +/// Credential signer_credential; +/// SignaturePublicKey signature_key; +/// uint64 signing_time; +/// opaque metadata; +/// /* SignWithLabel(., "ProtectedMetadataTBS",ProtectedMetadata) */ +/// opaque signature; +/// } ProtectedMetadata; +/// ``` +/// +/// This extension must be verified by the application every time it is set or +/// changed. +/// The application **MUST** verify that +/// * the signature is valid +/// * the credential has been valid at `signing_time` +/// * the `signer_application_id` is equal to the `creator_application_id`. +/// +/// FIXME: This should NOT be deserializable. But we need to change more code for +/// that to be possible. +#[derive( + PartialEq, Eq, Clone, Debug, Serialize, Deserialize, TlsDeserialize, TlsSerialize, TlsSize, +)] +pub struct ProtectedMetadata { + payload: ProtectedMetadataTbs, + signature: Signature, +} + +impl ProtectedMetadata { + /// Create a new protected metadata extension and sign it. + pub fn new( + signer: &impl Signer, + signer_application_id: Vec, + signer_credential: Credential, + signature_key: Vec, + metadata: Vec, + ) -> Result { + let tbs = ProtectedMetadataTbs::new( + signer_application_id, + signer_credential, + signature_key, + metadata, + ); + tbs.sign(signer) + } + + /// Get the signer application ID as slice. + pub fn signer_application_id(&self) -> &[u8] { + self.payload.signer_application_id.as_ref() + } + + /// Get the signer [`Credential`]. + pub fn signer_credential(&self) -> &Credential { + &self.payload.signer_credential + } + + /// Get the signature key as slize. + pub fn signature_key(&self) -> &[u8] { + self.payload.signature_key.as_ref() + } + + /// Get the signing time as UNIX timestamp. + pub fn signing_time(&self) -> u64 { + self.payload.signing_time + } + + /// Get the serialized metadata as slice. + /// + /// This is opaque to OpenMLS. The caller must handle it appropriately. + pub fn metadata(&self) -> &[u8] { + self.payload.metadata.as_ref() + } +} + +impl SignedStruct for ProtectedMetadata { + fn from_payload(payload: ProtectedMetadataTbs, signature: Signature) -> Self { + ProtectedMetadata { payload, signature } + } +} + +/// # Protected Metadata +/// +/// ```c +/// /* SignWithLabel(., "ProtectedMetadataTBS",ProtectedMetadata) */ +/// struct { +/// opaque signer_application_id; +/// Credential signer_credential; +/// SignaturePublicKey signature_key; +/// uint64 signing_time; +/// opaque metadata; +/// } ProtectedMetadataTBS; +/// ``` +#[derive( + PartialEq, Eq, Clone, Debug, Serialize, Deserialize, TlsSerialize, TlsDeserialize, TlsSize, +)] +pub struct ProtectedMetadataTbs { + signer_application_id: Vec, + signer_credential: Credential, + signature_key: Vec, + signing_time: u64, + metadata: Vec, +} + +impl ProtectedMetadataTbs { + /// Create a protected metadata extension tbs. + fn new( + signer_application_id: Vec, + signer_credential: Credential, + signature_key: Vec, + metadata: impl Into>, + ) -> Self { + Self { + signer_application_id, + signer_credential, + signature_key, + signing_time: SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("SystemTime before UNIX EPOCH!") + .as_secs(), + metadata: metadata.into(), + } + } +} + +const SIGNATURE_LABEL: &str = "ProtectedMetadataTbs"; + +impl Signable for ProtectedMetadataTbs { + type SignedOutput = ProtectedMetadata; + + fn unsigned_payload(&self) -> Result, tls_codec::Error> { + self.tls_serialize_detached() + } + + fn label(&self) -> &str { + SIGNATURE_LABEL + } +} + +/// XXX: This really should not be implemented on [`ProtectedMetadata`] but on +/// the verifiable version. +mod verifiable { + use super::*; + + impl Verifiable for ProtectedMetadata { + fn unsigned_payload(&self) -> Result, tls_codec::Error> { + self.payload.tls_serialize_detached() + } + + fn signature(&self) -> &Signature { + &self.signature + } + + fn label(&self) -> &str { + SIGNATURE_LABEL + } + } + + mod private_mod { + #[derive(Default)] + pub struct Seal; + } + + impl VerifiedStruct for ProtectedMetadata { + type SealingType = private_mod::Seal; + + fn from_verifiable(v: ProtectedMetadata, _seal: Self::SealingType) -> Self { + Self { + payload: v.payload, + signature: v.signature, + } + } + } +} + +mod xmtp_lib { + use super::*; + + #[derive( + PartialEq, Eq, Clone, Debug, Serialize, Deserialize, TlsSerialize, TlsDeserialize, TlsSize, + )] + #[repr(u8)] + pub enum ConversationType { + OneOnOne = 1, + Group, + } + + #[derive( + PartialEq, Eq, Clone, Debug, Serialize, Deserialize, TlsSerialize, TlsDeserialize, TlsSize, + )] + #[repr(u8)] + pub enum MemberModificationPolicy { + #[tls_codec(discriminant = 1)] + NoOne(), + Anyone(), + Creator(), + Admins(Vec), + } + + #[derive( + PartialEq, Eq, Clone, Debug, Serialize, Deserialize, TlsSerialize, TlsDeserialize, TlsSize, + )] + pub struct Admin { + application_id: Vec, + } + + impl From> for Admin { + fn from(application_id: Vec) -> Self { + Self { application_id } + } + } + + impl From<&[u8]> for Admin { + fn from(application_id: &[u8]) -> Self { + Self { + application_id: application_id.to_vec(), + } + } + } + + /// # XMTP Metadata + #[derive( + PartialEq, Eq, Clone, Debug, Serialize, Deserialize, TlsSerialize, TlsDeserialize, TlsSize, + )] + pub struct XmtpMetadata { + version: u16, + conversation_type: ConversationType, + creator_application_id: Vec, + add_policy: MemberModificationPolicy, + remove_policy: MemberModificationPolicy, + } + + impl XmtpMetadata { + /// Generate a new metadata struct. + pub fn new( + version: u16, + conversation_type: ConversationType, + creator_application_id: Vec, + add_policy: MemberModificationPolicy, + remove_policy: MemberModificationPolicy, + ) -> Self { + Self { + version, + conversation_type, + creator_application_id, + add_policy, + remove_policy, + } + } + + /// Get the version of this metadata. + pub fn version(&self) -> u16 { + self.version + } + + /// Get the [`ConversationType`]. + pub fn conversation_type(&self) -> &ConversationType { + &self.conversation_type + } + + /// Get the application ID of the creator as a slice. + pub fn creator_application_id(&self) -> &[u8] { + self.creator_application_id.as_ref() + } + + /// Get the [`MemberModificationPolicy`] for adding members to the group. + pub fn add_policy(&self) -> &MemberModificationPolicy { + &self.add_policy + } + + /// Get the [`MemberModificationPolicy`] for removing members from the group. + pub fn remove_policy(&self) -> &MemberModificationPolicy { + &self.remove_policy + } + } +} + +#[cfg(test)] +mod tests { + use tls_codec::{Deserialize, Serialize}; + + use crate::{ + credentials::test_utils::new_credential, extensions::protected_metadata::ProtectedMetadata, + prelude_test::OpenMlsSignaturePublicKey, test_utils::*, + }; + + use super::{ + xmtp_lib::{ConversationType, MemberModificationPolicy, XmtpMetadata}, + *, + }; + + #[test] + fn serialize_metadata() { + let metadata = XmtpMetadata::new( + 6, + ConversationType::Group, + b"MetadataTestAppId".to_vec(), + MemberModificationPolicy::Anyone(), + MemberModificationPolicy::Admins(vec![b"admin1"[..].into(), b"admin2"[..].into()]), + ); + let serialized = metadata.tls_serialize_detached().unwrap(); + let deserialized = XmtpMetadata::tls_deserialize_exact(serialized).unwrap(); + assert_eq!(deserialized, metadata); + } + + #[apply(ciphersuites_and_providers)] + fn serialize_extension(ciphersuite: Ciphersuite, provider: &impl OpenMlsProvider) { + let creator_application_id = b"MetadataTestAppId".to_vec(); + + // Create metadata + let metadata = XmtpMetadata::new( + 6, + ConversationType::Group, + creator_application_id.clone(), + MemberModificationPolicy::Anyone(), + MemberModificationPolicy::Admins(vec![b"admin1"[..].into(), b"admin2"[..].into()]), + ); + + // Setup crypto + let (credential_with_key, signer) = new_credential( + provider, + b"Kreator", + crate::credentials::CredentialType::Basic, + ciphersuite.signature_algorithm(), + ); + let signature_key = + OpenMlsSignaturePublicKey::new(signer.public().into(), ciphersuite.into()).unwrap(); + + let signer_application_id = creator_application_id.clone(); + let extension = ProtectedMetadata::new( + &signer, + signer_application_id, + credential_with_key.credential.clone(), + signature_key.as_slice().to_vec(), + metadata.tls_serialize_detached().unwrap(), + ) + .unwrap(); + + // serialize and deserialize + verify + let serialized = extension.tls_serialize_detached().unwrap(); + let unverified = ProtectedMetadata::tls_deserialize_exact(serialized).unwrap(); + let deserialized: ProtectedMetadata = unverified + .verify(provider.crypto(), &signature_key) + .unwrap(); + assert_eq!(deserialized, extension); + + let xmtp_metadata = XmtpMetadata::tls_deserialize_exact(deserialized.metadata()).unwrap(); + assert_eq!( + xmtp_metadata.add_policy(), + &MemberModificationPolicy::Anyone() + ); + } +} From 9747914d7861b0b6762760ccaec86d075d61c74b Mon Sep 17 00:00:00 2001 From: Nicholas Molnar <65710+neekolas@users.noreply.github.com> Date: Fri, 5 Jan 2024 08:57:12 -0800 Subject: [PATCH 2/2] Remove xmtp_lib --- openmls/src/extensions/protected_metadata.rs | 138 +------------------ 1 file changed, 5 insertions(+), 133 deletions(-) diff --git a/openmls/src/extensions/protected_metadata.rs b/openmls/src/extensions/protected_metadata.rs index a9aa8423ba..c87b79bad3 100644 --- a/openmls/src/extensions/protected_metadata.rs +++ b/openmls/src/extensions/protected_metadata.rs @@ -189,108 +189,6 @@ mod verifiable { } } -mod xmtp_lib { - use super::*; - - #[derive( - PartialEq, Eq, Clone, Debug, Serialize, Deserialize, TlsSerialize, TlsDeserialize, TlsSize, - )] - #[repr(u8)] - pub enum ConversationType { - OneOnOne = 1, - Group, - } - - #[derive( - PartialEq, Eq, Clone, Debug, Serialize, Deserialize, TlsSerialize, TlsDeserialize, TlsSize, - )] - #[repr(u8)] - pub enum MemberModificationPolicy { - #[tls_codec(discriminant = 1)] - NoOne(), - Anyone(), - Creator(), - Admins(Vec), - } - - #[derive( - PartialEq, Eq, Clone, Debug, Serialize, Deserialize, TlsSerialize, TlsDeserialize, TlsSize, - )] - pub struct Admin { - application_id: Vec, - } - - impl From> for Admin { - fn from(application_id: Vec) -> Self { - Self { application_id } - } - } - - impl From<&[u8]> for Admin { - fn from(application_id: &[u8]) -> Self { - Self { - application_id: application_id.to_vec(), - } - } - } - - /// # XMTP Metadata - #[derive( - PartialEq, Eq, Clone, Debug, Serialize, Deserialize, TlsSerialize, TlsDeserialize, TlsSize, - )] - pub struct XmtpMetadata { - version: u16, - conversation_type: ConversationType, - creator_application_id: Vec, - add_policy: MemberModificationPolicy, - remove_policy: MemberModificationPolicy, - } - - impl XmtpMetadata { - /// Generate a new metadata struct. - pub fn new( - version: u16, - conversation_type: ConversationType, - creator_application_id: Vec, - add_policy: MemberModificationPolicy, - remove_policy: MemberModificationPolicy, - ) -> Self { - Self { - version, - conversation_type, - creator_application_id, - add_policy, - remove_policy, - } - } - - /// Get the version of this metadata. - pub fn version(&self) -> u16 { - self.version - } - - /// Get the [`ConversationType`]. - pub fn conversation_type(&self) -> &ConversationType { - &self.conversation_type - } - - /// Get the application ID of the creator as a slice. - pub fn creator_application_id(&self) -> &[u8] { - self.creator_application_id.as_ref() - } - - /// Get the [`MemberModificationPolicy`] for adding members to the group. - pub fn add_policy(&self) -> &MemberModificationPolicy { - &self.add_policy - } - - /// Get the [`MemberModificationPolicy`] for removing members from the group. - pub fn remove_policy(&self) -> &MemberModificationPolicy { - &self.remove_policy - } - } -} - #[cfg(test)] mod tests { use tls_codec::{Deserialize, Serialize}; @@ -300,37 +198,14 @@ mod tests { prelude_test::OpenMlsSignaturePublicKey, test_utils::*, }; - use super::{ - xmtp_lib::{ConversationType, MemberModificationPolicy, XmtpMetadata}, - *, - }; - - #[test] - fn serialize_metadata() { - let metadata = XmtpMetadata::new( - 6, - ConversationType::Group, - b"MetadataTestAppId".to_vec(), - MemberModificationPolicy::Anyone(), - MemberModificationPolicy::Admins(vec![b"admin1"[..].into(), b"admin2"[..].into()]), - ); - let serialized = metadata.tls_serialize_detached().unwrap(); - let deserialized = XmtpMetadata::tls_deserialize_exact(serialized).unwrap(); - assert_eq!(deserialized, metadata); - } + use super::*; #[apply(ciphersuites_and_providers)] fn serialize_extension(ciphersuite: Ciphersuite, provider: &impl OpenMlsProvider) { let creator_application_id = b"MetadataTestAppId".to_vec(); // Create metadata - let metadata = XmtpMetadata::new( - 6, - ConversationType::Group, - creator_application_id.clone(), - MemberModificationPolicy::Anyone(), - MemberModificationPolicy::Admins(vec![b"admin1"[..].into(), b"admin2"[..].into()]), - ); + let metadata = vec![1, 2, 3]; // Setup crypto let (credential_with_key, signer) = new_credential( @@ -348,7 +223,7 @@ mod tests { signer_application_id, credential_with_key.credential.clone(), signature_key.as_slice().to_vec(), - metadata.tls_serialize_detached().unwrap(), + metadata.clone(), ) .unwrap(); @@ -360,10 +235,7 @@ mod tests { .unwrap(); assert_eq!(deserialized, extension); - let xmtp_metadata = XmtpMetadata::tls_deserialize_exact(deserialized.metadata()).unwrap(); - assert_eq!( - xmtp_metadata.add_policy(), - &MemberModificationPolicy::Anyone() - ); + let xmtp_metadata = deserialized.metadata(); + assert_eq!(xmtp_metadata, metadata); } }