Skip to main content

fractal/secret/
file.rs

1//! API to store the session tokens in a file on the system.
2
3use std::path::Path;
4
5use matrix_sdk_store_encryption::{EncryptedValue, StoreCipher};
6use serde::{Deserialize, Serialize, de::DeserializeOwned};
7use tokio::fs;
8
9/// An API to read from or write to a file that encodes its content.
10pub(super) struct SecretFile;
11
12impl SecretFile {
13    /// Read a secret from the file at the given path.
14    pub(super) async fn read<T: DeserializeOwned>(
15        path: &Path,
16        passphrase: &str,
17    ) -> Result<T, SecretFileError> {
18        let (cipher, encrypted_secret) = Self::read_inner(path, passphrase).await?;
19        let serialized_secret = cipher.decrypt_value_data(encrypted_secret)?;
20        Ok(rmp_serde::from_slice(&serialized_secret)?)
21    }
22
23    async fn read_inner(
24        path: &Path,
25        passphrase: &str,
26    ) -> Result<(StoreCipher, EncryptedValue), SecretFileError> {
27        let bytes = fs::read(&path).await?;
28        let content = rmp_serde::from_slice::<SecretFileContent>(&bytes)?;
29        let cipher = StoreCipher::import(passphrase, &content.encrypted_cipher)?;
30        Ok((cipher, content.encrypted_secret))
31    }
32
33    /// Get the existing cipher at the given path, or create a new one.
34    async fn get_or_create_cipher(
35        path: &Path,
36        passphrase: &str,
37    ) -> Result<StoreCipher, SecretFileError> {
38        let cipher = match Self::read_inner(path, passphrase).await {
39            Ok((cipher, _)) => cipher,
40            Err(_) => StoreCipher::new()?,
41        };
42        Ok(cipher)
43    }
44
45    /// Write a secret to the file at the given path.
46    pub(super) async fn write<T: Serialize>(
47        path: &Path,
48        passphrase: &str,
49        secret: &T,
50    ) -> Result<(), SecretFileError> {
51        let cipher = Self::get_or_create_cipher(path, passphrase).await?;
52        // `StoreCipher::encrypt_value()` uses JSON to serialize the data, which shows
53        // in the content of the file. To have a more opaque format, we use
54        // `rmp_serde::to_vec()` which will not show the fields of
55        // `EncryptedValue`.
56        let encrypted_secret = cipher.encrypt_value_data(rmp_serde::to_vec_named(secret)?)?;
57        let encrypted_cipher = cipher.export(passphrase)?;
58        let bytes = rmp_serde::to_vec(&SecretFileContent {
59            encrypted_cipher,
60            encrypted_secret,
61        })?;
62        fs::write(path, bytes).await?;
63        Ok(())
64    }
65}
66
67#[derive(Serialize, Deserialize)]
68struct SecretFileContent {
69    #[serde(with = "serde_bytes")]
70    encrypted_cipher: Vec<u8>,
71    encrypted_secret: EncryptedValue,
72}
73
74/// All errors that can occur when interacting with a secret file.
75#[derive(Debug, thiserror::Error)]
76pub(super) enum SecretFileError {
77    /// An error occurred when accessing the file.
78    #[error(transparent)]
79    File(#[from] std::io::Error),
80
81    /// An error occurred when decoding the content.
82    #[error(transparent)]
83    Decode(#[from] rmp_serde::decode::Error),
84
85    /// An error occurred when encoding the content.
86    #[error(transparent)]
87    Encode(#[from] rmp_serde::encode::Error),
88
89    /// An error occurred when encrypting or decrypting the content.
90    #[error(transparent)]
91    Encryption(#[from] matrix_sdk_store_encryption::Error),
92}