use std::fmt;
use matrix_sdk_base::crypto::{CrossSigningKeyExport, secret_storage::SecretStorageKey};
use ruma::{
events::{
GlobalAccountDataEventType, secret::request::SecretName,
secret_storage::secret::SecretEventContent,
},
serde::Raw,
};
use serde_json::value::to_raw_value;
use tracing::{
Span, error,
field::{debug, display},
info, instrument, warn,
};
use zeroize::Zeroize;
use super::{DecryptionError, Result, SecretStorageError};
use crate::Client;
#[cfg_attr(doc, aquamarine::aquamarine)]
pub struct SecretStore {
pub(super) client: Client,
pub(super) key: SecretStorageKey,
}
impl SecretStore {
pub fn secret_storage_key(&self) -> String {
self.key.to_base58()
}
pub async fn get_secret(&self, secret_name: impl Into<SecretName>) -> Result<Option<String>> {
let secret_name = secret_name.into();
let event_type = GlobalAccountDataEventType::from(secret_name.to_owned());
if let Some(secret_content) = self
.client
.account()
.fetch_account_data(event_type)
.await
.map_err(|e| SecretStorageError::into_import_error(secret_name.clone(), e))?
{
let mut secret_content = secret_content
.deserialize_as_unchecked::<SecretEventContent>()
.map_err(|e| SecretStorageError::into_import_error(secret_name.clone(), e))?;
if let Some(secret_content) = secret_content.encrypted.remove(self.key.key_id()) {
let decrypted = self
.key
.decrypt(
&secret_content.try_into().map_err(|e| {
SecretStorageError::into_import_error(secret_name.clone(), e)
})?,
&secret_name,
)
.map_err(DecryptionError::from)
.map_err(|e| SecretStorageError::into_import_error(secret_name.clone(), e))?;
let secret = String::from_utf8(decrypted)
.map_err(DecryptionError::from)
.map_err(|e| SecretStorageError::into_import_error(secret_name.clone(), e))?;
Ok(Some(secret))
} else {
Ok(None)
}
} else {
Ok(None)
}
}
pub async fn put_secret(&self, secret_name: impl Into<SecretName>, secret: &str) -> Result<()> {
let _guard = self.client.locks().store_secret_lock.lock().await;
let secret_name = secret_name.into();
let event_type = GlobalAccountDataEventType::from(secret_name.to_owned());
let mut secret_content = if let Some(secret_content) =
self.client.account().fetch_account_data(event_type.to_owned()).await?
{
secret_content
.deserialize_as_unchecked::<SecretEventContent>()
.unwrap_or_else(|_| SecretEventContent::new(Default::default()))
} else {
SecretEventContent::new(Default::default())
};
let secret = secret.as_bytes().to_vec();
let encrypted_secret = self.key.encrypt(secret, &secret_name);
secret_content.encrypted.insert(self.key.key_id().to_owned(), encrypted_secret.into());
let secret_content = Raw::from_json(to_raw_value(&secret_content)?);
self.client.account().set_account_data_raw(event_type, secret_content).await?;
Ok(())
}
async fn get_cross_signing_keys(&self) -> Result<CrossSigningKeyExport> {
let mut export = CrossSigningKeyExport::default();
export.master_key = self.get_secret(SecretName::CrossSigningMasterKey).await?;
export.self_signing_key = self.get_secret(SecretName::CrossSigningSelfSigningKey).await?;
export.user_signing_key = self.get_secret(SecretName::CrossSigningUserSigningKey).await?;
Ok(export)
}
async fn put_cross_signing_keys(&self, export: CrossSigningKeyExport) -> Result<()> {
if let Some(master_key) = &export.master_key {
self.put_secret(SecretName::CrossSigningMasterKey, master_key).await?;
}
if let Some(user_signing_key) = &export.user_signing_key {
self.put_secret(SecretName::CrossSigningUserSigningKey, user_signing_key).await?;
}
if let Some(self_signing_key) = &export.self_signing_key {
self.put_secret(SecretName::CrossSigningSelfSigningKey, self_signing_key).await?;
}
Ok(())
}
async fn maybe_enable_backups(&self) -> Result<()> {
match self.get_secret(SecretName::RecoveryKey).await {
Ok(Some(mut secret)) => {
let ret =
self.client.encryption().backups().maybe_enable_backups(&secret).await.map_err(
|e| SecretStorageError::into_import_error(SecretName::RecoveryKey, e),
);
if let Err(e) = &ret {
warn!("Could not enable backups from secret storage: {e:?}");
}
secret.zeroize();
Ok(ret.map(|_| ())?)
}
Err(e) => {
warn!("Could not enable backups from secret storage: {e:?}");
Err(e)
}
_ => {
info!("No backup recovery key found.");
Ok(())
}
}
}
#[instrument(fields(user_id, device_id, cross_signing_status))]
pub async fn import_secrets(&self) -> Result<()> {
let olm_machine = self.client.olm_machine().await;
let olm_machine = olm_machine.as_ref().ok_or(crate::Error::NoOlmMachine)?;
Span::current()
.record("user_id", display(olm_machine.user_id()))
.record("device_id", display(olm_machine.device_id()));
info!("Fetching the private cross-signing keys from the secret store");
let export = self.get_cross_signing_keys().await?;
info!(cross_signing_keys = ?export, "Received the cross signing keys from the server");
let (request_id, request) = olm_machine.query_keys_for_users([olm_machine.user_id()]);
self.client.keys_query(&request_id, request.device_keys).await?;
let status = olm_machine
.import_cross_signing_keys(export)
.await
.map_err(SecretStorageError::from_secret_import_error)?;
Span::current().record("cross_signing_status", debug(&status));
info!("Done importing the cross signing keys");
if status.has_self_signing {
info!("Successfully imported the self-signing key, attempting to sign our own device");
if let Some(own_device) = self.client.encryption().get_own_device().await? {
own_device.verify().await?;
let (request_id, request) =
olm_machine.query_keys_for_users([olm_machine.user_id()]);
self.client.keys_query(&request_id, request.device_keys).await?;
info!("Successfully signed our own device, the device is now verified");
} else {
error!("Couldn't find our own device in the store");
}
}
self.maybe_enable_backups().await?;
Ok(())
}
pub(super) async fn export_secrets(&self) -> Result<()> {
let olm_machine = self.client.olm_machine().await;
let olm_machine = olm_machine.as_ref().ok_or(crate::Error::NoOlmMachine)?;
if let Some(cross_signing_keys) = olm_machine.export_cross_signing_keys().await? {
self.put_cross_signing_keys(cross_signing_keys).await?;
}
let backup_keys = olm_machine.backup_machine().get_backup_keys().await?;
if let Some(backup_recovery_key) = backup_keys.decryption_key {
let mut key = backup_recovery_key.to_base64();
self.put_secret(SecretName::RecoveryKey, &key).await?;
key.zeroize();
}
Ok(())
}
}
impl fmt::Debug for SecretStore {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SecretStore").field("key", &self.key).finish_non_exhaustive()
}
}