Skip to content

Commit

Permalink
feat(crypto-bindings): Save/Load dehydrated pickle key
Browse files Browse the repository at this point in the history
  • Loading branch information
BillCarsonFr committed Dec 13, 2024
1 parent 789bd31 commit 39b9531
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 23 deletions.
78 changes: 59 additions & 19 deletions bindings/matrix-sdk-crypto-ffi/src/dehydrated_devices.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ use matrix_sdk_crypto::{
DehydratedDevice as InnerDehydratedDevice, DehydratedDevices as InnerDehydratedDevices,
RehydratedDevice as InnerRehydratedDevice,
},
store::DehydratedDeviceKey,
store::DehydratedDeviceKey as InnerDehydratedDeviceKey,
};
use ruma::{api::client::dehydrated_device, events::AnyToDeviceEvent, serde::Raw, OwnedDeviceId};
use serde_json::json;
use tokio::runtime::Handle;
use zeroize::Zeroize;

use crate::{CryptoStoreError, DehydratedDeviceKey};

#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi(flat_error)]
Expand All @@ -25,6 +26,8 @@ pub enum DehydrationError {
Store(#[from] matrix_sdk_crypto::CryptoStoreError),
#[error("The pickle key has an invalid length, expected 32 bytes, got {0}")]
PickleKeyLength(usize),
#[error(transparent)]
Rand(#[from] rand::Error),
}

impl From<matrix_sdk_crypto::dehydrated_devices::DehydrationError> for DehydrationError {
Expand All @@ -36,6 +39,9 @@ impl From<matrix_sdk_crypto::dehydrated_devices::DehydrationError> for Dehydrati
Self::MissingSigningKey(e)
}
matrix_sdk_crypto::dehydrated_devices::DehydrationError::Store(e) => Self::Store(e),
matrix_sdk_crypto::dehydrated_devices::DehydrationError::PickleKeyLength(l) => {
Self::PickleKeyLength(l)
}
}
}
}
Expand Down Expand Up @@ -69,14 +75,14 @@ impl DehydratedDevices {

pub fn rehydrate(
&self,
pickle_key: Vec<u8>,
pickle_key: &DehydratedDeviceKey,
device_id: String,
device_data: String,
) -> Result<Arc<RehydratedDevice>, DehydrationError> {
let device_data: Raw<_> = serde_json::from_str(&device_data)?;
let device_id: OwnedDeviceId = device_id.into();

let mut key = get_pickle_key(&pickle_key)?;
let key = InnerDehydratedDeviceKey::from_slice(&pickle_key.inner)?;

let ret = RehydratedDevice {
runtime: self.runtime.to_owned(),
Expand All @@ -88,10 +94,41 @@ impl DehydratedDevices {
}
.into();

key.zeroize();

Ok(ret)
}

/// Get the cached dehydrated device pickle key if any.
///
/// None if the key was not previously cached (via
/// [`Self::save_dehydrated_device_pickle_key`]).
///
/// Should be used to periodically rotate the dehydrated device to avoid
/// OTK exhaustion and accumulation of to_device messages.
pub fn get_dehydrated_device_key(
&self,
) -> Result<Option<crate::DehydratedDeviceKey>, CryptoStoreError> {
Ok(self
.runtime
.block_on(self.inner.get_dehydrated_device_pickle_key())?
.map(crate::DehydratedDeviceKey::from))
}

/// Store the dehydrated device pickle key in the crypto store.
///
/// This is useful if the client wants to periodically rotate dehydrated
/// devices to avoid OTK exhaustion and accumulated to_device problems.
pub fn save_dehydrated_device_key(
&self,
pickle_key: &crate::DehydratedDeviceKey,
) -> Result<(), CryptoStoreError> {
let pickle_key = InnerDehydratedDeviceKey::from_slice(&pickle_key.inner)?;
Ok(self.runtime.block_on(self.inner.save_dehydrated_device_pickle_key(&pickle_key))?)
}

/// Deletes the previously stored dehydrated device pickle key.
pub fn delete_dehydrated_device_key(&self) -> Result<(), CryptoStoreError> {
Ok(self.runtime.block_on(self.inner.delete_dehydrated_device_pickle_key())?)
}
}

#[derive(uniffi::Object)]
Expand Down Expand Up @@ -141,15 +178,13 @@ impl DehydratedDevice {
pub fn keys_for_upload(
&self,
device_display_name: String,
pickle_key: Vec<u8>,
pickle_key: &DehydratedDeviceKey,
) -> Result<UploadDehydratedDeviceRequest, DehydrationError> {
let mut key = get_pickle_key(&pickle_key)?;
let key = InnerDehydratedDeviceKey::from_slice(&pickle_key.inner)?;

let request =
self.runtime.block_on(self.inner.keys_for_upload(device_display_name, &key))?;

key.zeroize();

Ok(request.into())
}
}
Expand Down Expand Up @@ -180,15 +215,20 @@ impl From<dehydrated_device::put_dehydrated_device::unstable::Request>
}
}

fn get_pickle_key(pickle_key: &[u8]) -> Result<DehydratedDeviceKey, DehydrationError> {
let pickle_key_length = pickle_key.len();
#[cfg(test)]
mod tests {
use crate::DehydratedDeviceKey;

#[test]
fn test_dehydrated_key() {
let result = DehydratedDeviceKey::new();
assert!(result.is_ok());
let dehydrated_device_key = result.unwrap();
let base_64 = dehydrated_device_key.to_base64();
let inner_bytes = dehydrated_device_key.inner;

let copy = DehydratedDeviceKey::from_slice(&inner_bytes).unwrap();

if pickle_key_length == 32 {
let mut raw_bytes = [0u8; 32];
raw_bytes.copy_from_slice(pickle_key);
let key = DehydratedDeviceKey::from_bytes(&raw_bytes);
Ok(key)
} else {
Err(DehydrationError::PickleKeyLength(pickle_key_length))
assert_eq!(base_64, copy.to_base64());
}
}
7 changes: 5 additions & 2 deletions bindings/matrix-sdk-crypto-ffi/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
#![allow(missing_docs)]

use matrix_sdk_crypto::{
store::CryptoStoreError as InnerStoreError, KeyExportError, MegolmError, OlmError,
SecretImportError as RustSecretImportError, SignatureError as InnerSignatureError,
store::{CryptoStoreError as InnerStoreError, DehydrationError as InnerDehydrationError},
KeyExportError, MegolmError, OlmError, SecretImportError as RustSecretImportError,
SignatureError as InnerSignatureError,
};
use matrix_sdk_sqlite::OpenStoreError;
use ruma::{IdParseError, OwnedUserId};
Expand Down Expand Up @@ -57,6 +58,8 @@ pub enum CryptoStoreError {
InvalidUserId(String, IdParseError),
#[error(transparent)]
Identifier(#[from] IdParseError),
#[error(transparent)]
DehydrationError(#[from] InnerDehydrationError),
}

#[derive(Debug, thiserror::Error, uniffi::Error)]
Expand Down
40 changes: 39 additions & 1 deletion bindings/matrix-sdk-crypto-ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ pub use machine::{KeyRequestPair, OlmMachine, SignatureVerification};
use matrix_sdk_common::deserialized_responses::{ShieldState as RustShieldState, ShieldStateCode};
use matrix_sdk_crypto::{
olm::{IdentityKeys, InboundGroupSession, SenderData, Session},
store::{Changes, CryptoStore, PendingChanges, RoomSettings as RustRoomSettings},
store::{
Changes, CryptoStore, DehydratedDeviceKey as InnerDehydratedDeviceKey, PendingChanges,
RoomSettings as RustRoomSettings,
},
types::{
DeviceKey, DeviceKeys, EventEncryptionAlgorithm as RustEventEncryptionAlgorithm, SigningKey,
},
Expand All @@ -62,6 +65,8 @@ pub use verification::{
};
use vodozemac::{Curve25519PublicKey, Ed25519PublicKey};

use crate::dehydrated_devices::DehydrationError;

/// Struct collecting data that is important to migrate to the rust-sdk
#[derive(Deserialize, Serialize, uniffi::Record)]
pub struct MigrationData {
Expand Down Expand Up @@ -822,6 +827,39 @@ impl TryFrom<matrix_sdk_crypto::store::BackupKeys> for BackupKeys {
}
}

/// Dehydrated device key
#[derive(uniffi::Record, Clone)]
pub struct DehydratedDeviceKey {
pub(crate) inner: Vec<u8>,
}

impl DehydratedDeviceKey {
/// Generates a new random pickle key.
pub fn new() -> Result<Self, DehydrationError> {
let inner = InnerDehydratedDeviceKey::new()?;
Ok(inner.into())
}

/// Creates a new dehydration pickle key from the given slice.
///
/// Fail if the slice length is not 32.
pub fn from_slice(slice: &[u8]) -> Result<Self, DehydrationError> {
let inner = InnerDehydratedDeviceKey::from_slice(slice)?;
Ok(inner.into())
}

/// Export the [`DehydratedDeviceKey`] as a base64 encoded string.
pub fn to_base64(&self) -> String {
let inner = InnerDehydratedDeviceKey::from_slice(&self.inner).unwrap();
inner.to_base64()
}
}
impl From<InnerDehydratedDeviceKey> for DehydratedDeviceKey {
fn from(pickle_key: InnerDehydratedDeviceKey) -> Self {
DehydratedDeviceKey { inner: pickle_key.into() }
}
}

impl From<matrix_sdk_crypto::store::RoomKeyCounts> for RoomKeyCounts {
fn from(count: matrix_sdk_crypto::store::RoomKeyCounts) -> Self {
Self { total: count.total as i64, backed_up: count.backed_up as i64 }
Expand Down
4 changes: 4 additions & 0 deletions crates/matrix-sdk-crypto/src/dehydrated_devices.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ pub enum DehydrationError {
#[error(transparent)]
Pickle(#[from] LibolmPickleError),

/// The pickle key has an invalid length
#[error("The pickle key has an invalid length, expected 32 bytes, got {0}")]
PickleKeyLength(usize),

/// The dehydrated device could not be signed by our user identity,
/// we're missing the self-signing key.
#[error("The self-signing key is missing, can't create a dehydrated device")]
Expand Down
30 changes: 29 additions & 1 deletion crates/matrix-sdk-crypto/src/store/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,10 @@ use matrix_sdk_common::{store_locks::CrossProcessStoreLock, timeout::timeout};
pub use memorystore::MemoryStore;
pub use traits::{CryptoStore, DynCryptoStore, IntoCryptoStore};

pub use crate::gossiping::{GossipRequest, SecretInfo};
pub use crate::{
dehydrated_devices::DehydrationError,
gossiping::{GossipRequest, SecretInfo},
};

/// A wrapper for our CryptoStore trait object.
///
Expand Down Expand Up @@ -775,6 +778,19 @@ impl DehydratedDeviceKey {
Ok(Self { inner: key })
}

/// Creates a new dehydration pickle key from the given slice.
///
/// Fail if the slice length is not 32.
pub fn from_slice(slice: &[u8]) -> Result<Self, DehydrationError> {
if slice.len() == 32 {
let mut key = Box::new([0u8; 32]);
key.copy_from_slice(slice);
Ok(DehydratedDeviceKey { inner: key })
} else {
Err(DehydrationError::PickleKeyLength(slice.len()))
}
}

/// Creates a dehydration pickle key from the given bytes.
pub fn from_bytes(raw_key: &[u8; 32]) -> Self {
let mut inner = Box::new([0u8; Self::KEY_SIZE]);
Expand All @@ -789,6 +805,18 @@ impl DehydratedDeviceKey {
}
}

impl From<&[u8; 32]> for DehydratedDeviceKey {
fn from(value: &[u8; 32]) -> Self {
DehydratedDeviceKey { inner: Box::new(*value) }
}
}

impl From<DehydratedDeviceKey> for Vec<u8> {
fn from(key: DehydratedDeviceKey) -> Self {
key.inner.to_vec()
}
}

#[cfg(not(tarpaulin_include))]
impl Debug for DehydratedDeviceKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Expand Down

0 comments on commit 39b9531

Please sign in to comment.