Skip to content

Commit

Permalink
Merge pull request #9 from xmtp/nm/protected-metadata
Browse files Browse the repository at this point in the history
Protected metadata
  • Loading branch information
neekolas authored Jan 5, 2024
2 parents 9c7d5cb + 9d04758 commit f864dfc
Show file tree
Hide file tree
Showing 3 changed files with 282 additions and 1 deletion.
7 changes: 6 additions & 1 deletion openmls/src/extensions/codec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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(),
};

Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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()))
}
Expand Down
35 changes: 35 additions & 0 deletions openmls/src/extensions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand All @@ -49,6 +50,8 @@ pub use last_resort::LastResortExtension;
pub use ratchet_tree_extension::RatchetTreeExtension;
pub use required_capabilities::RequiredCapabilitiesExtension;

pub use protected_metadata::ProtectedMetadata;

#[cfg(test)]
mod test_extensions;

Expand Down Expand Up @@ -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),
}
Expand Down Expand Up @@ -132,6 +139,7 @@ impl From<u16> for ExtensionType {
4 => ExtensionType::ExternalPub,
5 => ExtensionType::ExternalSenders,
10 => ExtensionType::LastResort,
11 => ExtensionType::ProtectedMetadata,
unknown => ExtensionType::Unknown(unknown),
}
}
Expand All @@ -146,6 +154,7 @@ impl From<ExtensionType> for u16 {
ExtensionType::ExternalPub => 4,
ExtensionType::ExternalSenders => 5,
ExtensionType::LastResort => 10,
ExtensionType::ProtectedMetadata => 11,
ExtensionType::Unknown(unknown) => unknown,
}
}
Expand All @@ -162,6 +171,7 @@ impl ExtensionType {
| ExtensionType::ExternalPub
| ExtensionType::ExternalSenders
| ExtensionType::LastResort
| ExtensionType::ProtectedMetadata
)
}
}
Expand Down Expand Up @@ -200,6 +210,9 @@ pub enum Extension {
/// A [`LastResortExtension`]
LastResort(LastResortExtension),

/// A [`ProtectedMetadata`] extension
ProtectedMetadata(ProtectedMetadata),

/// A currently unknown extension.
Unknown(u16, UnknownExtension),
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand All @@ -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),
}
}
Expand Down
241 changes: 241 additions & 0 deletions openmls/src/extensions/protected_metadata.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
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<V>;
/// Credential signer_credential;
/// SignaturePublicKey signature_key;
/// uint64 signing_time;
/// opaque metadata<V>;
/// /* SignWithLabel(., "ProtectedMetadataTBS",ProtectedMetadata) */
/// opaque signature<V>;
/// } 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<u8>,
signer_credential: Credential,
signature_key: Vec<u8>,
metadata: Vec<u8>,
) -> Result<Self, SignatureError> {
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<ProtectedMetadataTbs> for ProtectedMetadata {
fn from_payload(payload: ProtectedMetadataTbs, signature: Signature) -> Self {
ProtectedMetadata { payload, signature }
}
}

/// # Protected Metadata
///
/// ```c
/// /* SignWithLabel(., "ProtectedMetadataTBS",ProtectedMetadata) */
/// struct {
/// opaque signer_application_id<V>;
/// Credential signer_credential;
/// SignaturePublicKey signature_key;
/// uint64 signing_time;
/// opaque metadata<V>;
/// } ProtectedMetadataTBS;
/// ```
#[derive(
PartialEq, Eq, Clone, Debug, Serialize, Deserialize, TlsSerialize, TlsDeserialize, TlsSize,
)]
pub struct ProtectedMetadataTbs {
signer_application_id: Vec<u8>,
signer_credential: Credential,
signature_key: Vec<u8>,
signing_time: u64,
metadata: Vec<u8>,
}

impl ProtectedMetadataTbs {
/// Create a protected metadata extension tbs.
fn new(
signer_application_id: Vec<u8>,
signer_credential: Credential,
signature_key: Vec<u8>,
metadata: impl Into<Vec<u8>>,
) -> 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<Vec<u8>, 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<Vec<u8>, 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<ProtectedMetadata> for ProtectedMetadata {
type SealingType = private_mod::Seal;

fn from_verifiable(v: ProtectedMetadata, _seal: Self::SealingType) -> Self {
Self {
payload: v.payload,
signature: v.signature,
}
}
}
}

#[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::*;

#[apply(ciphersuites_and_providers)]
fn serialize_extension(ciphersuite: Ciphersuite, provider: &impl OpenMlsProvider) {
let creator_application_id = b"MetadataTestAppId".to_vec();

// Create metadata
let metadata = vec![1, 2, 3];

// 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.clone(),
)
.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 = deserialized.metadata();
assert_eq!(xmtp_metadata, metadata);
}
}

0 comments on commit f864dfc

Please sign in to comment.