(feat): call crypto migration
This commit is contained in:
parent
1c89e71606
commit
9323daa34a
6 changed files with 329 additions and 49 deletions
|
|
@ -1,4 +1,4 @@
|
|||
use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue};
|
||||
use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataTypeId, DataValue, TypeMap};
|
||||
use serde_json::Map;
|
||||
|
||||
use std::{collections::BTreeMap, env, sync::Arc, time::Duration};
|
||||
|
|
@ -16,7 +16,84 @@ pub struct CallGroup {
|
|||
pub show: RwLock<bool>,
|
||||
pub anonymous_joining: RwLock<bool>,
|
||||
pub short_link: RwLock<Option<String>>,
|
||||
pub secrets: RwLock<BTreeMap<(u64, u64), String>>,
|
||||
pub secrets: RwLock<BTreeMap<u64, CallSecretEnvelope>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct CallSecretEnvelope {
|
||||
pub secret_id: String,
|
||||
pub version_number: i64,
|
||||
pub encrypted_secret: Vec<u8>,
|
||||
pub kem_ciphertext: Vec<u8>,
|
||||
pub wrapping_scheme: String,
|
||||
}
|
||||
|
||||
impl CallSecretEnvelope {
|
||||
pub fn from_data_value(value: &DataValue) -> Option<Self> {
|
||||
let tm = TypeMap::latest();
|
||||
let secret_id = value
|
||||
.get_field(DataType::SecretId.to_id(&tm))?
|
||||
.as_str()?
|
||||
.to_string();
|
||||
let version_number = value
|
||||
.get_field(DataType::VersionNumber.to_id(&tm))?
|
||||
.as_signed_number()
|
||||
.and_then(|n| i64::try_from(n).ok())
|
||||
.or_else(|| {
|
||||
value
|
||||
.get_field(DataType::VersionNumber.to_id(&tm))?
|
||||
.as_number()
|
||||
.and_then(|n| i64::try_from(n).ok())
|
||||
})?;
|
||||
let encrypted_secret = value
|
||||
.get_field(DataType::EncryptedSecret.to_id(&tm))?
|
||||
.as_bytes()?;
|
||||
let kem_ciphertext = value
|
||||
.get_field(DataType::KemCiphertext.to_id(&tm))?
|
||||
.as_bytes()?;
|
||||
let wrapping_scheme = value
|
||||
.get_field(DataType::WrappingScheme.to_id(&tm))?
|
||||
.as_str()?
|
||||
.to_string();
|
||||
|
||||
Some(Self {
|
||||
secret_id,
|
||||
version_number,
|
||||
encrypted_secret,
|
||||
kem_ciphertext,
|
||||
wrapping_scheme,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn to_data_value(&self) -> DataValue {
|
||||
let tm = TypeMap::latest();
|
||||
let mut map: BTreeMap<DataTypeId, DataValue> = BTreeMap::new();
|
||||
map.insert(
|
||||
DataType::SecretId.to_id(&tm),
|
||||
DataValue::Str(self.secret_id.clone()),
|
||||
);
|
||||
map.insert(
|
||||
DataType::VersionNumber.to_id(&tm),
|
||||
DataValue::SignedNumber(self.version_number.into()),
|
||||
);
|
||||
map.insert(
|
||||
DataType::EncryptedSecret.to_id(&tm),
|
||||
DataValue::Bytes(self.encrypted_secret.clone()),
|
||||
);
|
||||
map.insert(
|
||||
DataType::KemCiphertext.to_id(&tm),
|
||||
DataValue::Bytes(self.kem_ciphertext.clone()),
|
||||
);
|
||||
map.insert(
|
||||
DataType::WrappingScheme.to_id(&tm),
|
||||
DataValue::Str(self.wrapping_scheme.clone()),
|
||||
);
|
||||
DataValue::container_from_map(&map)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn call_invite_secret_from_cv(cv: &CommunicationValue) -> Option<CallSecretEnvelope> {
|
||||
CallSecretEnvelope::from_data_value(cv.get_data(DataType::CallSecret))
|
||||
}
|
||||
|
||||
impl CallGroup {
|
||||
|
|
@ -40,6 +117,10 @@ impl CallGroup {
|
|||
.cloned()
|
||||
}
|
||||
|
||||
pub async fn get_secret_for_user(&self, user_id: u64) -> Option<CallSecretEnvelope> {
|
||||
self.secrets.read().await.get(&user_id).cloned()
|
||||
}
|
||||
|
||||
pub async fn is_anonymous(&self) -> bool {
|
||||
*self.anonymous_joining.read().await
|
||||
}
|
||||
|
|
@ -154,3 +235,110 @@ impl CallGroup {
|
|||
self.short_link.read().await.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn envelope(label: &str) -> CallSecretEnvelope {
|
||||
CallSecretEnvelope {
|
||||
secret_id: format!("call:test:{label}"),
|
||||
version_number: 1,
|
||||
encrypted_secret: format!("encrypted:{label}").into_bytes(),
|
||||
kem_ciphertext: format!("kem:{label}").into_bytes(),
|
||||
wrapping_scheme: "mtp-call-secret-kem-chacha20poly1305-hkdf-sha256-v1".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_secret_plain_string_is_rejected() {
|
||||
assert!(
|
||||
CallSecretEnvelope::from_data_value(&DataValue::Str("old-secret".to_string()))
|
||||
.is_none()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_secret_missing_required_field_is_rejected() {
|
||||
let tm = TypeMap::latest();
|
||||
let mut map: BTreeMap<DataTypeId, DataValue> = BTreeMap::new();
|
||||
map.insert(
|
||||
DataType::SecretId.to_id(&tm),
|
||||
DataValue::Str("call:test:main".to_string()),
|
||||
);
|
||||
map.insert(
|
||||
DataType::VersionNumber.to_id(&tm),
|
||||
DataValue::SignedNumber(1),
|
||||
);
|
||||
map.insert(
|
||||
DataType::EncryptedSecret.to_id(&tm),
|
||||
DataValue::Bytes(vec![1, 2, 3]),
|
||||
);
|
||||
map.insert(
|
||||
DataType::WrappingScheme.to_id(&tm),
|
||||
DataValue::Str("mtp-call-secret-kem-chacha20poly1305-hkdf-sha256-v1".to_string()),
|
||||
);
|
||||
|
||||
assert!(
|
||||
CallSecretEnvelope::from_data_value(&DataValue::container_from_map(&map)).is_none()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_secret_envelope_round_trips_opaque_bytes() {
|
||||
let secret = envelope("receiver");
|
||||
|
||||
assert_eq!(
|
||||
CallSecretEnvelope::from_data_value(&secret.to_data_value()),
|
||||
Some(secret)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_invite_missing_call_secret_is_rejected() {
|
||||
let cv = CommunicationValue::new(CommunicationType::CallInvite);
|
||||
|
||||
assert!(call_invite_secret_from_cv(&cv).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_invite_parses_call_secret_envelope() {
|
||||
let secret = envelope("receiver");
|
||||
let cv = CommunicationValue::new(CommunicationType::CallInvite)
|
||||
.add_typed_default(DataType::CallSecret, secret.clone().to_data_value());
|
||||
|
||||
assert_eq!(call_invite_secret_from_cv(&cv), Some(secret));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn receiver_call_invite_contains_receiver_envelope_unchanged() {
|
||||
let receiver_secret = envelope("receiver");
|
||||
let cv = CommunicationValue::new(CommunicationType::CallInvite).add_typed_default(
|
||||
DataType::CallSecret,
|
||||
receiver_secret.clone().to_data_value(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
CallSecretEnvelope::from_data_value(cv.get_data(DataType::CallSecret)),
|
||||
Some(receiver_secret)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn call_group_returns_only_current_users_secret() {
|
||||
let call_id = Uuid::new_v4();
|
||||
let group = CallGroup::new(call_id, Arc::new(Caller::new(1, call_id, true)));
|
||||
let own_secret = envelope("own");
|
||||
let receiver_secret = envelope("receiver");
|
||||
|
||||
{
|
||||
let mut secrets = group.secrets.write().await;
|
||||
secrets.insert(1, own_secret.clone());
|
||||
secrets.insert(2, receiver_secret.clone());
|
||||
}
|
||||
|
||||
assert_eq!(group.get_secret_for_user(1).await, Some(own_secret));
|
||||
assert_eq!(group.get_secret_for_user(2).await, Some(receiver_secret));
|
||||
assert_eq!(group.get_secret_for_user(3).await, None);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue